Skip to content

Commit

Permalink
atc: test flight override annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmdm committed Jan 26, 2025
1 parent 4440234 commit 9a393cb
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 55 deletions.
1 change: 1 addition & 0 deletions cmd/atc/internal/testing/Dockerfile.wasmcache
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ COPY . .
RUN \
GOOS=wasip1 GOARCH=wasm go build -o ./cmd/atc/internal/testing/wasmcache/wasm/flight.v1.wasm ./cmd/atc/internal/testing/apis/backend/v1/flight && \
GOOS=wasip1 GOARCH=wasm go build -o ./cmd/atc/internal/testing/wasmcache/wasm/flight.v2.wasm ./cmd/atc/internal/testing/apis/backend/v2/flight && \
GOOS=wasip1 GOARCH=wasm go build -o ./cmd/atc/internal/testing/wasmcache/wasm/flight.dev.wasm ./cmd/atc/internal/testing/apis/backend/v2/dev && \
GOOS=wasip1 GOARCH=wasm go build -o ./cmd/atc/internal/testing/wasmcache/wasm/converter.wasm ./cmd/atc/internal/testing/apis/backend/converter && \
go build -o ./bin/server ./cmd/atc/internal/testing/wasmcache

Expand Down
4 changes: 2 additions & 2 deletions cmd/atc/internal/testing/apis/backend/converter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func V1ToV2(source v1.Backend) v2.Backend {
Annotations: source.Annotations,
},
Spec: v2.BackendSpec{
Image: source.Spec.Image,
Img: source.Spec.Image,
Replicas: source.Spec.Replicas,
Meta: v2.Meta{
Labels: source.Spec.Labels,
Expand All @@ -169,7 +169,7 @@ func V2ToV1(source v2.Backend) v1.Backend {
Annotations: annotations,
},
Spec: v1.BackendSpec{
Image: source.Spec.Image,
Image: source.Spec.Img,
Replicas: source.Spec.Replicas,
Labels: source.Spec.Meta.Labels,
NodePort: source.Spec.NodePort,
Expand Down
8 changes: 4 additions & 4 deletions cmd/atc/internal/testing/apis/backend/v2/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ type Backend struct {
}

type Meta struct {
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`
Labels map[string]string `json:"labels,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
}
type BackendSpec struct {
// Image has a breaking change in that `image` has been renamed to `img`
Image string `json:"img"`
// Img has a breaking change in that `image` has been renamed to `img`
Img string `json:"img"`
Replicas int32 `json:"replicas"`
// Meta differs from the previous version which only accepted a Labels field. Now it is within meta.
Meta Meta `json:"meta,omitempty"`
Expand Down
125 changes: 125 additions & 0 deletions cmd/atc/internal/testing/apis/backend/v2/dev/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package main

import (
"cmp"
"encoding/json"
"fmt"
"io"
"maps"
"os"
"strconv"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/yaml"

v2 "github.com/yokecd/yoke/cmd/atc/internal/testing/apis/backend/v2"
)

func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

func run() error {
var backend v2.Backend
if err := yaml.NewYAMLToJSONDecoder(os.Stdin).Decode(&backend); err != nil && err != io.EOF {
return err
}

backend.Spec.ServicePort = cmp.Or(backend.Spec.ServicePort, 3000)

if backend.Spec.Meta.Labels == nil {
backend.Spec.Meta.Labels = map[string]string{}
}

maps.Copy(backend.Spec.Meta.Labels, selector(backend))

return json.NewEncoder(os.Stdout).Encode([]any{
createDeamonSet(backend),
createService(backend),
})
}

func createDeamonSet(backend v2.Backend) *appsv1.DaemonSet {
return &appsv1.DaemonSet{
TypeMeta: metav1.TypeMeta{
APIVersion: appsv1.SchemeGroupVersion.Identifier(),
Kind: "DaemonSet",
},
ObjectMeta: metav1.ObjectMeta{
Name: backend.Name,
Namespace: backend.Namespace,
Labels: backend.Spec.Meta.Labels,
},
Spec: appsv1.DaemonSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: selector(backend)},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: backend.Spec.Meta.Labels,
Annotations: backend.Spec.Meta.Annotations,
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: backend.Name,
Image: backend.Spec.Img,
ImagePullPolicy: corev1.PullIfNotPresent,
Env: []corev1.EnvVar{
{
Name: "PORT",
Value: strconv.Itoa(backend.Spec.ServicePort),
},
},
Ports: []corev1.ContainerPort{
{
Name: backend.Name,
Protocol: corev1.ProtocolTCP,
ContainerPort: int32(backend.Spec.ServicePort),
},
},
},
},
},
},
},
}
}

func createService(backend v2.Backend) *corev1.Service {
return &corev1.Service{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.Identifier(),
Kind: "Service",
},
ObjectMeta: metav1.ObjectMeta{
Name: backend.Name,
Namespace: backend.Namespace,
},
Spec: corev1.ServiceSpec{
Selector: selector(backend),
Type: func() corev1.ServiceType {
if backend.Spec.NodePort > 0 {
return corev1.ServiceTypeNodePort
}
return corev1.ServiceTypeClusterIP
}(),
Ports: []corev1.ServicePort{
{
Protocol: corev1.ProtocolTCP,
NodePort: int32(backend.Spec.NodePort),
Port: 80,
TargetPort: intstr.FromString(backend.Name),
},
},
},
}
}

func selector(backend v2.Backend) map[string]string {
return map[string]string{"app": backend.Name}
}
2 changes: 1 addition & 1 deletion cmd/atc/internal/testing/apis/backend/v2/flight/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func createDeployment(backend v2.Backend) *appsv1.Deployment {
Containers: []corev1.Container{
{
Name: backend.Name,
Image: backend.Spec.Image,
Image: backend.Spec.Img,
ImagePullPolicy: corev1.PullIfNotPresent,
Env: []corev1.EnvVar{
{
Expand Down
96 changes: 83 additions & 13 deletions cmd/atc/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/yokecd/yoke/internal/testutils"
"github.com/yokecd/yoke/internal/x"
"github.com/yokecd/yoke/pkg/apis/airway/v1alpha1"
"github.com/yokecd/yoke/pkg/flight"
"github.com/yokecd/yoke/pkg/openapi"
"github.com/yokecd/yoke/pkg/yoke"
)
Expand Down Expand Up @@ -382,6 +383,7 @@ func TestAirTrafficController(t *testing.T) {
"failed to detect new Backend version",
)

// ALthough we create a v1 version we will be able to fetch it as a v2 version.
require.NoError(
t,
commander.Takeoff(ctx, yoke.TakeoffParams{
Expand All @@ -403,31 +405,99 @@ func TestAirTrafficController(t *testing.T) {
}),
)

if setupOnly, _ := strconv.ParseBool(os.Getenv("SETUP_ONLY")); setupOnly {
return
}
getC4ts := func() backendv2.Backend {
rawBackend, err := client.Dynamic.
Resource(schema.GroupVersionResource{Group: "examples.com", Version: "v2", Resource: "backends"}).
Namespace("default").
Get(context.Background(), "c4ts", metav1.GetOptions{})

rawBackend, err := client.Dynamic.
Resource(schema.GroupVersionResource{Group: "examples.com", Version: "v2", Resource: "backends"}).
Namespace("default").
Get(context.Background(), "c4ts", metav1.GetOptions{})
require.NoError(t, err)

require.NoError(t, err)
var bv2 backendv2.Backend
require.NoError(t, runtime.DefaultUnstructuredConverter.FromUnstructured(rawBackend.Object, &bv2))

var bv2 backendv2.Backend
require.NoError(t, runtime.DefaultUnstructuredConverter.FromUnstructured(rawBackend.Object, &bv2))
return bv2
}

require.Equal(
t,
backendv2.BackendSpec{
Image: "yokecd/c4ts:test",
Img: "yokecd/c4ts:test",
Replicas: 1,
Meta: backendv2.Meta{
Labels: map[string]string{"test.app": "c4ts"},
Annotations: map[string]string{},
Annotations: nil,
},
},
bv2.Spec,
getC4ts().Spec,
)

testutils.EventuallyNoErrorf(
t,
func() error {
deployments, err := client.Clientset.AppsV1().Deployments("default").List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
if count := len(deployments.Items); count != 1 {
return fmt.Errorf("expected 1 deployment but got %d", count)
}
return nil
},
time.Second,
30*time.Second,
"failed to view backend deployment",
)

if setupOnly, _ := strconv.ParseBool(os.Getenv("SETUP_ONLY")); setupOnly {
return
}

require.NoError(
t,
commander.Takeoff(ctx, yoke.TakeoffParams{
Release: "c4ts",
Flight: yoke.FlightParams{
Input: testutils.JsonReader(backendv2.Backend{
ObjectMeta: metav1.ObjectMeta{
Name: "c4ts",
Annotations: map[string]string{
flight.AnnotationOverrideFlight: "http://wasmcache/flight.dev.wasm",
},
},
Spec: backendv2.BackendSpec{
Img: "yokecd/c4ts:test",
Replicas: 1,
},
}),
},
Wait: 30 * time.Second,
Poll: time.Second,
}),
)

testutils.EventuallyNoErrorf(
t,
func() error {
deployments, err := client.Clientset.AppsV1().Deployments("default").List(ctx, metav1.ListOptions{})
if err != nil {
return fmt.Errorf("failed to list deployments: %w", err)
}
if count := len(deployments.Items); count != 0 {
return fmt.Errorf("expected no deployments but got: %d", count)
}
daemonsets, err := client.Clientset.AppsV1().DaemonSets("default").List(ctx, metav1.ListOptions{})
if err != nil {
return fmt.Errorf("failed to list deployments: %w", err)
}
if count := len(daemonsets.Items); count != 1 {
return fmt.Errorf("expected 1 daemonsets but got: %d", count)
}
return nil
},
time.Second,
30*time.Second,
"failed to see dev wasm take over",
)

airway, err := airwayIntf.Get(context.Background(), "backends.examples.com", metav1.GetOptions{})
Expand Down
38 changes: 10 additions & 28 deletions internal/atc/atc.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,39 +508,11 @@ func (atc atc) FlightReconciler(params FlightReconcilerParams) ctrl.HandleFunc {
params.Flight.RLock()
defer params.Flight.RUnlock()

module, err := func() (*wasi.Module, error) {
overrideURL, _, _ := unstructured.NestedString(resource.Object, "metadata", "annotations", flight.AnnotationOverrideFlight)
if overrideURL == "" {
return params.Flight.Module, nil
}

ctrl.Logger(ctx).Warn("using override module", "url", overrideURL)

// LoadWasm every time and compile. As much as it is tempting to introduce a caching layer, we must abstain.
// The override url serves to test fliht implementations that differ from the Airway's defined module; the content
// served does not need to be static. There is a performance hit here, but it is not meant to be the production approach
// to deploying changes.
wasm, err := yoke.LoadWasm(ctx, overrideURL)
if err != nil {
return nil, fmt.Errorf("failed to load override wasm module: %w", err)
}

mod, err := wasi.Compile(ctx, wasi.CompileParams{Wasm: wasm})
if err != nil {
return nil, fmt.Errorf("failed to compile override wasm module: %w", err)
}
return &mod, nil
}()
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get wasm module: %w", err)
}

commander := yoke.FromK8Client(ctrl.Client(ctx))

takeoffParams := yoke.TakeoffParams{
Release: ReleaseName(resource),
Flight: yoke.FlightParams{
Module: module,
Input: bytes.NewReader(data),
Namespace: event.Namespace,
},
Expand All @@ -555,6 +527,16 @@ func (atc atc) FlightReconciler(params FlightReconcilerParams) ctrl.HandleFunc {
},
}

if overrideURL, _, _ := unstructured.NestedString(resource.Object, "metadata", "annotations", flight.AnnotationOverrideFlight); overrideURL != "" {
ctrl.Logger(ctx).Warn("using override module", "url", overrideURL)
// Simply set the override URL as the flight path and let yoke load and execute the wasm module as if called from the command line.
// We do not want to manually compile the module here or cache it, since this feature is for overrides that will be most often used in testing;
// It is not recommended to override in production. As so it is allowable that users don't version the overrideURL and that the content can change.
takeoffParams.Flight.Path = overrideURL
} else {
takeoffParams.Flight.Module = params.Flight.Module
}

flightStatus("InProgress", "Flight is taking off")

if err := commander.Takeoff(ctx, takeoffParams); err != nil {
Expand Down
10 changes: 8 additions & 2 deletions internal/k8s/ctrl/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Event struct {
Namespace string

attempts int
typ string
}

type Result struct {
Expand Down Expand Up @@ -145,8 +146,12 @@ func (ctrl Instance) Run() error {

logger := Logger(ctrl.ctx).With(
slog.String("loopId", randHex()),
slog.String("event", event.String()),
slog.Int("attempt", event.attempts),
slog.Group(
"event",
"name", event.String(),
"attempt", event.attempts,
"type", event.typ,
),
)

// It is important that we do not cancel the handler mid-execution.
Expand Down Expand Up @@ -257,6 +262,7 @@ func (ctrl Instance) eventsFromMetaGetter(ctx context.Context, getter metadata.G
evt := Event{
Name: metadata.Name,
Namespace: metadata.Namespace,
typ: string(event.Type),
}

if event.Type == watch.Modified || event.Type == watch.Added {
Expand Down
Loading

0 comments on commit 9a393cb

Please sign in to comment.