Skip to content

Commit

Permalink
WIP: Integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nstogner committed Nov 6, 2023
1 parent f8d78c2 commit bd5399d
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bin
17 changes: 17 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
ENVTEST_K8S_VERSION = 1.27.1

.PHONY: test
test-integration: envtest
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -v

## Location to install dependencies to
LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)
ENVTEST ?= $(LOCALBIN)/setup-envtest

.PHONY: envtest
envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
$(ENVTEST): $(LOCALBIN)
test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest

4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.16.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.25.0 // indirect
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
Expand All @@ -57,7 +59,7 @@ require (
k8s.io/component-base v0.28.3 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/controller-runtime v0.16.3 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
Expand All @@ -93,6 +94,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
Expand Down Expand Up @@ -193,6 +196,8 @@ k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5Ohx
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4=
sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
Expand Down
137 changes: 137 additions & 0 deletions integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package main

import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"sync"
"sync/atomic"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
disv1 "k8s.io/api/discovery/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
)

func TestIntegration(t *testing.T) {
const modelName = "test-model-a"
deploy := testDeployment(modelName)

require.NoError(t, testK8sClient.Create(testCtx, deploy))

backendRequests := &atomic.Int32{}
testBackend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
backendRequests.Add(1)
w.WriteHeader(200)
}))
testBackendURL, err := url.Parse(testBackend.URL)
require.NoError(t, err)
testBackendPort, err := strconv.Atoi(testBackendURL.Port())
require.NoError(t, testK8sClient.Create(testCtx, endpointSlice(modelName, testBackendURL.Hostname(), int32(testBackendPort))))

var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(3 * time.Second)

body := []byte(fmt.Sprintf(`{"model": %q}`, modelName))
req, err := http.NewRequest(http.MethodPost, testServer.URL, bytes.NewReader(body))
requireNoError(err)

res, err := testHTTPClient.Do(req)
require.NoError(t, err)
require.Equal(t, 200, res.StatusCode)
require.Equal(t, int32(1), backendRequests.Load())
}()

require.EventuallyWithT(t, func(t *assert.CollectT) {
err := testK8sClient.Get(testCtx, types.NamespacedName{Namespace: deploy.Namespace, Name: deploy.Name}, deploy)
assert.NoError(t, err, "getting the deployment")
assert.GreaterOrEqual(t, deploy.Spec.Replicas, 1, "scale-up should have occurred")
}, 3*time.Second, time.Second/2, "waiting for the deployment to be scaled up")

t.Logf("Waiting for wait group")
wg.Wait()
}

func testDeployment(name string) *appsv1.Deployment {
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: name + "-deploy",
Namespace: testNamespace,
Labels: map[string]string{
"app": name,
},
Annotations: map[string]string{
"lingo.substratus.ai/models": name,
},
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": name,
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": name,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "model",
Image: "some-model:1.2.3",
Ports: []corev1.ContainerPort{
{
ContainerPort: 80,
},
},
},
},
},
},
},
}
}

func endpointSlice(name, ip string, port int32) *disv1.EndpointSlice {
return &disv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
Name: name + "-deploy",
Namespace: testNamespace,
Labels: map[string]string{
disv1.LabelServiceName: name + "-deploy",
},
},
AddressType: disv1.AddressTypeIPv4,
Endpoints: []disv1.Endpoint{
{
Addresses: []string{ip},
Conditions: disv1.EndpointConditions{
Ready: ptr.To(true),
Serving: ptr.To(true),
Terminating: ptr.To(false),
},
},
},
Ports: []disv1.EndpointPort{
{
Name: ptr.To("http"),
Port: ptr.To(port),
Protocol: ptr.To(corev1.ProtocolTCP),
},
},
}
}
105 changes: 105 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package main

import (
"context"
"log"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
)

var (
testNamespace = "test"
testK8sClient client.Client
testEnv *envtest.Environment
testCtx context.Context
testCancel context.CancelFunc
testServer *httptest.Server
testHTTPClient = &http.Client{Timeout: 10 * time.Second}
)

func TestMain(m *testing.M) {
testCtx, testCancel = context.WithCancel(context.TODO())

log.Println("bootstrapping test environment")
testEnv = &envtest.Environment{}
cfg, err := testEnv.Start()
requireNoError(err)

requireNoError(clientgoscheme.AddToScheme(scheme))

testK8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
requireNoError(err)

requireNoError(testK8sClient.Create(testCtx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: testNamespace},
}))

mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme,
Metrics: metricsserver.Options{
BindAddress: "0",
},
})
requireNoError(err)

fifo := NewFIFOQueueManager(1, 1000)

endpoints, err := NewEndpointsManager(mgr)
requireNoError(err)
endpoints.EndpointSizeCallback = fifo.UpdateQueueSize

scaler, err := NewDeploymentManager(mgr)
requireNoError(err)
scaler.Namespace = testNamespace
scaler.ScaleDownPeriod = 30 * time.Second

autoscaler := NewAutoscaler()
autoscaler.Interval = 3 * time.Second
autoscaler.AverageCount = 10 // 10 * 3 seconds = 30 sec avg
autoscaler.Scaler = scaler
autoscaler.FIFO = fifo
go autoscaler.Start()

handler := &Handler{
Deployments: scaler,
Endpoints: endpoints,
FIFO: fifo,
}
testServer = httptest.NewServer(handler)
defer testServer.Close()

ctx := ctrl.SetupSignalHandler()

go func() {
log.Println("starting manager")
requireNoError(mgr.Start(ctx))
}()

log.Println("running tests")
code := m.Run()

// TODO: Run cleanup on ctrl-C, etc.
log.Println("stopping manager")
testCancel()
log.Println("stopping test environment")
requireNoError(testEnv.Stop())

os.Exit(code)
}

func requireNoError(err error) {
if err != nil {
log.Fatal(err)
}
}

0 comments on commit bd5399d

Please sign in to comment.