Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
70a748c
feat(racer): add controller and ClusterVolume API
jveski Oct 6, 2026
9726099
build(racer): package standalone controller and deployment
jveski Oct 6, 2026
dd7693d
fix(racer): reject credential replay before reconciliation writes
jveski Oct 6, 2026
6ff1ff5
fix(racer): reject topology replay before committing counters
jveski Oct 6, 2026
af4415e
fix(racer): detect unavailable Rust interop test target
jveski Oct 6, 2026
c64d6d1
fix(racer): compare stable state in scale no-op test
jveski Oct 6, 2026
6f302ea
fix(racer): reject credential replay during bootstrap issuance
jveski Oct 6, 2026
5a505df
fix(racer): reject credential replay before catalog filtering
jveski Oct 6, 2026
513583b
fix(racer): resolve formatting and module CI failures
jveski Oct 6, 2026
e7be887
docs(racer): document Pod-bound token prerequisites
jveski Oct 6, 2026
bc52d94
fix(notice): include direct UUID dependency
jveski Oct 6, 2026
ed50552
refactor(racer): sync complete controller snapshot from ffeae9b0
jveski Oct 7, 2026
01f687b
refactor(racer): keep PR controller-only with external workload fixtures
jveski Oct 7, 2026
0afbb1d
fix(racer): reject authority replay before signing and durable writes
jveski Oct 7, 2026
eb44e32
fix(racer): restrict runtime ConfigMap and Lease RBAC
jveski Oct 7, 2026
6343d6a
feat(racer): expose TLS admission lifecycle metrics
jveski Oct 7, 2026
9f2bcfe
fix(racer): queue only changed recovery hints
jveski Oct 7, 2026
a4c0f81
refactor(racer): remove redundant response writer wrappers
jveski Oct 7, 2026
700f782
test(racer): exercise authority operations without duplicate reconcilers
jveski Oct 7, 2026
4a1c192
fix(racer): include deployment envtests in CI target
jveski Oct 7, 2026
adc3d09
fix(racer): restrict controller Node writes to enrollment annotations
jveski Oct 7, 2026
b8a2e1f
test(racer): exercise runtime admission policy with envtest
jveski Oct 7, 2026
d142ba0
refactor(racer): replace ClusterVolume with ClusterCache
jveski Oct 7, 2026
842281e
refactor(racer): remove standalone deployment
jveski Oct 7, 2026
07c1f61
docs(racer): simplify ClusterCache introduction
jveski Oct 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,22 @@ jobs:

- name: Validate exact pure runtime test group
run: timeout --signal=TERM --kill-after=10s 300s python3 hack/scripts/runtime-miri.py ${{ matrix.group }}
racer-envtest:
name: Racer API Server Integration Tests
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
cache-dependency-path: go.sum

- name: Provision envtest and run controller API-server race tests
run: timeout --signal=TERM --kill-after=10s 300s make racer-envtest-ci

# ---------- Orca Integration Tests ----------
# Spins up Garage and Azurite via testcontainers-go and runs the
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ jobs:
file: images/unbounded-operator/Containerfile
- name: gantry
file: images/gantry/Containerfile
- name: racer-controller
file: images/racer-controller/Containerfile
- name: orca
file: images/orca/Containerfile
- name: inventory-aggregator
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,10 @@ jobs:
file: images/gantry/Containerfile
platforms: linux/amd64,linux/arm64
trivy: true
- name: racer-controller
file: images/racer-controller/Containerfile
platforms: linux/amd64,linux/arm64
trivy: true
- name: host-ubuntu2404
file: images/host-ubuntu2404/Containerfile
platforms: linux/amd64,linux/arm64
Expand Down
42 changes: 42 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ GOBUILD=$(GOCMD) build
GOTEST=$(GOCMD) test
GOMOD=$(GOCMD) mod
GOLINT=golangci-lint run -c .golangci.yaml
ENVTEST_K8S_VERSION ?= 1.37.0
SETUP_ENVTEST_VERSION ?= v0.25.2-0.20260923145615-d837464d41be
SETUP_ENVTEST = $(CURDIR)/bin/setup-envtest-$(SETUP_ENVTEST_VERSION)
GO_PACKAGE_PATTERNS=./api/... ./cmd/... ./deploy/... ./e2e/... ./hack/... ./internal/... ./pkg/...
# e2e packages hold nothing but files behind the e2e build tag, so `go list`
# needs the tag to see them at all. Without it they are silently skipped by
Expand Down Expand Up @@ -356,6 +359,14 @@ help: ## Show this help
@echo "Documentation:"
@echo " docs-serve Start local Hugo dev server"
@echo ""
@echo "Racer Controller:"
@echo " racer-controller Test and build the Go controller"
@echo " racer-controller-build Build the Go controller without lint/test"
@echo " racer-test Lint and race-test the controller and deployment contracts"
@echo " racer-envtest Run controller API-server tests with KUBEBUILDER_ASSETS"
@echo " racer-envtest-ci Provision pinned assets and run controller API-server tests"
@echo " racer-generate Generate Racer deepcopy and CRD artifacts"
@echo ""
@echo "Common variables (override with VAR=value):"
@echo " VERSION=$(VERSION)"
@echo " GIT_COMMIT=$(GIT_COMMIT)"
Expand Down Expand Up @@ -541,6 +552,37 @@ e2e-gantry: $(HELM) ## Run the kind-based Gantry e2e suite
e2e-playpen: ## Run the kind-based playpen e2e suite
$(GOTEST) -tags=e2e ./e2e/playpen -v -timeout=10m

.PHONY: racer-controller racer-controller-build racer-test racer-server-test racer-envtest racer-envtest-ci racer-generate
racer-controller: racer-server-test racer-controller-build ## Test and build the Racer controller

racer-controller-build: ## Build the Racer controller without lint/test
@mkdir -p bin
timeout --signal=TERM --kill-after=10s 300s $(GOBUILD) -trimpath -ldflags '$(STAMP_LDFLAGS)' -o bin/racer-controller ./cmd/racer-controller

racer-server-test: ## Lint and race-test the Racer server
timeout --signal=TERM --kill-after=10s 300s $(GOLINT) ./api/racer/... ./internal/racer/... ./cmd/racer-controller/...
timeout --signal=TERM --kill-after=10s 300s $(GOTEST) -timeout=5m -race ./api/racer/... ./internal/racer/... ./cmd/racer-controller/...

racer-test: racer-server-test ## Check the Racer controller

$(SETUP_ENVTEST):
@mkdir -p bin tmp/envtest-tools
TMPDIR="$(CURDIR)/tmp/envtest-tools" GOBIN="$(CURDIR)/bin" timeout --signal=TERM --kill-after=10s 300s $(GOCMD) install sigs.k8s.io/controller-runtime/tools/setup-envtest@$(SETUP_ENVTEST_VERSION)
mv bin/setup-envtest "$(SETUP_ENVTEST)"

racer-envtest-ci: $(SETUP_ENVTEST) ## Provision pinned local API-server assets and require Racer envtest
@mkdir -p tmp/racer-envtest
@assets=$$(TMPDIR="$(CURDIR)/tmp/racer-envtest" timeout --signal=TERM --kill-after=10s 300s "$(SETUP_ENVTEST)" use $(ENVTEST_K8S_VERSION) --bin-dir "$(CURDIR)/bin/envtest" -p path) && \
$(MAKE) racer-envtest KUBEBUILDER_ASSETS="$$assets"

racer-envtest: ## Run real API-server, manager election, TLS and crash-recovery tests
@test -n "$(KUBEBUILDER_ASSETS)" || { echo "Set KUBEBUILDER_ASSETS to repository-local envtest binaries"; exit 1; }
@mkdir -p tmp/racer-envtest
TMPDIR="$(CURDIR)/tmp/racer-envtest" KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" timeout --signal=TERM --kill-after=10s 300s $(GOTEST) -race ./internal/racer ./internal/racer/authority -run '^TestEnvtest' -count=1 -v -timeout=5m

racer-generate: ## Generate Racer deepcopy and CRD artifacts
timeout --signal=TERM --kill-after=10s 300s $(GOCMD) generate ./api/racer/v1alpha1

build: machina-manifests token-refresher-manifests machine-ops-manifests playpen-manifests net-manifests unbounded-operator-manifests gantry-manifests ## Build all Go packages
$(GOBUILD) ./...

Expand Down
7 changes: 7 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,13 @@ notices:
license:
- name: Apache License, Version 2.0
link: https://github.com/google/renameio/blob/v2.0.2/LICENSE
- dependency: github.com/google/uuid
ecosystem: go
copyright:
- Copyright (c) 2009,2014 Google Inc. All rights reserved.
license:
- name: BSD 3-Clause License
link: https://github.com/google/uuid/blob/v1.6.0/LICENSE
- dependency: github.com/insomniacslk/dhcp
ecosystem: go
copyright:
Expand Down
43 changes: 43 additions & 0 deletions api/racer/v1alpha1/clustercache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0

package v1alpha1

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)

func TestClusterCacheRegistrationAndJSON(t *testing.T) {
scheme := runtime.NewScheme()
require.NoError(t, AddToScheme(scheme))

for kind, expected := range map[string]runtime.Object{
"ClusterCache": &ClusterCache{},
"ClusterCacheList": &ClusterCacheList{},
} {
object, err := scheme.New(GroupVersion.WithKind(kind))
require.NoError(t, err)
require.IsType(t, expected, object)
}

cache := &ClusterCache{ObjectMeta: metav1.ObjectMeta{Name: "cache", Labels: map[string]string{"test": "original"}}}
data, err := json.Marshal(cache)
require.NoError(t, err)

var object map[string]any
require.NoError(t, json.Unmarshal(data, &object))
require.NotContains(t, object, "spec")

copy := cache.DeepCopy()
copy.Labels["test"] = "copy"
require.Equal(t, "original", cache.Labels["test"])
list := &ClusterCacheList{Items: []ClusterCache{*cache}}
listCopy := list.DeepCopy()
listCopy.Items[0].Labels["test"] = "list-copy"
require.Equal(t, "original", list.Items[0].Labels["test"])
}
28 changes: 28 additions & 0 deletions api/racer/v1alpha1/clustercache_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0

package v1alpha1

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

// ClusterCache names a cache. Its Kubernetes UID is its wire identity.
// Socket paths are derived from metadata.name. Client access is controlled by pod volume mounts.
// The name limit keeps /run/racer/<name>/origin/socket within Linux sockaddr_un.
// Each DNS label is limited to 63 characters to match the wire contract;
// Kubernetes metadata validation enforces DNS subdomain spelling.
// The bounded regex checks label lengths without a costly CEL split/all loop.
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster,shortName=ccache
// +kubebuilder:validation:XValidation:rule="size(self.metadata.name) <= 82",message="name must fit the canonical Unix socket path"
// +kubebuilder:validation:XValidation:rule="self.metadata.name.matches('^[^.]{1,63}([.][^.]{1,63})*$')",message="each name label must be at most 63 characters"
type ClusterCache struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
}

// +kubebuilder:object:root=true
type ClusterCacheList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ClusterCache `json:"items"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.20.1
name: clustercaches.racer.unbounded-cloud.io
spec:
group: racer.unbounded-cloud.io
names:
kind: ClusterCache
listKind: ClusterCacheList
plural: clustercaches
shortNames:
- ccache
singular: clustercache
scope: Cluster
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: |-
ClusterCache names a cache. Its Kubernetes UID is its wire identity.
Socket paths are derived from metadata.name. Client access is controlled by pod volume mounts.
The name limit keeps /run/racer/<name>/origin/socket within Linux sockaddr_un.
Each DNS label is limited to 63 characters to match the wire contract;
Kubernetes metadata validation enforces DNS subdomain spelling.
The bounded regex checks label lengths without a costly CEL split/all loop.
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
type: object
x-kubernetes-validations:
- message: name must fit the canonical Unix socket path
rule: size(self.metadata.name) <= 82
- message: each name label must be at most 63 characters
rule: self.metadata.name.matches('^[^.]{1,63}([.][^.]{1,63})*$')
served: true
storage: true
9 changes: 9 additions & 0 deletions api/racer/v1alpha1/groupversion_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0

// +kubebuilder:object:generate=true
// +groupName=racer.unbounded-cloud.io
package v1alpha1

//go:generate go tool controller-gen object:headerFile=../../../hack/boilerplate.go.txt paths=.
//go:generate go tool controller-gen crd:headerFile=../../../hack/boilerplate.yaml.txt paths=. output:crd:dir=crd
23 changes: 23 additions & 0 deletions api/racer/v1alpha1/register.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0

package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)

const GroupName = "racer.unbounded-cloud.io"

var (
GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
SchemeBuilder = runtime.NewSchemeBuilder(func(s *runtime.Scheme) error {
s.AddKnownTypes(GroupVersion, &ClusterCache{}, &ClusterCacheList{})
metav1.AddToGroupVersion(s, GroupVersion)

return nil
})
AddToScheme = SchemeBuilder.AddToScheme
)
69 changes: 69 additions & 0 deletions api/racer/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 28 additions & 3 deletions cmd/racer-controller/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
# Racer Controller

Placeholder for the replacement Racer controller implementation.
The Racer controller keeps track of Racer nodes and named caches in Kubernetes.

The previous Racer implementation has been removed. The replacement is not
included in this branch.
## ClusterCache

A `ClusterCache` gives a Racer cache a name. It applies to the whole cluster,
not a single namespace, and has no `spec` fields to configure.

```yaml
apiVersion: racer.unbounded-cloud.io/v1alpha1
kind: ClusterCache
metadata:
name: images
```

Use a lowercase DNS name, such as `images` or `team.images`. Names can be up to
82 characters long, with at most 63 characters in each dot-separated part.

List caches with:

```sh
kubectl get clustercaches
```

Applications access a cache through its client socket at
`/run/racer/<name>/client/socket`. Pod volume mounts control access; creating a
`ClusterCache` does not grant every Pod access to it.

Deleting and recreating a `ClusterCache` creates a new cache identity, even if
you reuse the name.
Loading
Loading