171 lines
6.1 KiB
Go
171 lines
6.1 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestInClusterKubeClientMissingEnv(t *testing.T) {
|
|
t.Setenv("KUBERNETES_SERVICE_HOST", "")
|
|
t.Setenv("KUBERNETES_SERVICE_PORT", "")
|
|
if _, err := inClusterKubeClient(); err == nil {
|
|
t.Fatal("expected inClusterKubeClient error without env")
|
|
}
|
|
}
|
|
|
|
func TestKubeClientAndPodHelpers(t *testing.T) {
|
|
kube := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/nodes":
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"items": []any{
|
|
map[string]any{
|
|
"metadata": map[string]any{"name": "b", "labels": map[string]string{"kubernetes.io/arch": "arm64", "node-role.kubernetes.io/worker": "true"}},
|
|
"spec": map[string]any{"unschedulable": false},
|
|
},
|
|
map[string]any{
|
|
"metadata": map[string]any{"name": "a", "labels": map[string]string{"kubernetes.io/arch": "arm64", "node-role.kubernetes.io/worker": "true"}},
|
|
"spec": map[string]any{"unschedulable": false},
|
|
},
|
|
},
|
|
})
|
|
case r.Method == http.MethodPost && strings.Contains(r.URL.Path, "/pods"):
|
|
w.WriteHeader(http.StatusCreated)
|
|
case r.Method == http.MethodDelete && strings.Contains(r.URL.Path, "/nodes/"):
|
|
w.WriteHeader(http.StatusNotFound)
|
|
case r.Method == http.MethodGet && strings.Contains(r.URL.Path, "/pods/") && strings.HasSuffix(r.URL.Path, "/log"):
|
|
http.Error(w, "proxy error from 127.0.0.1:6443", http.StatusBadGateway)
|
|
case r.Method == http.MethodGet && strings.Contains(r.URL.Path, "/pods/"):
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"metadata": map[string]any{"name": filepath.Base(r.URL.Path)},
|
|
"status": map[string]any{
|
|
"phase": "Failed",
|
|
"reason": "CrashLoopBackOff",
|
|
"message": "boom",
|
|
"containerStatuses": []any{
|
|
map[string]any{
|
|
"state": map[string]any{
|
|
"waiting": map[string]any{"reason": "ImagePullBackOff", "message": "pulling"},
|
|
"terminated": map[string]any{"reason": "Completed", "message": "done"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}))
|
|
defer kube.Close()
|
|
|
|
client := kubeClientFactoryForURL(kube.URL, kube.Client())
|
|
if err := client.jsonRequest(http.MethodGet, "/api/v1/nodes", nil, &map[string]any{}); err != nil {
|
|
t.Fatalf("jsonRequest: %v", err)
|
|
}
|
|
if err := client.deleteRequest("/api/v1/nodes/a"); err != nil {
|
|
t.Fatalf("deleteRequest 404 should be nil: %v", err)
|
|
}
|
|
if err := client.jsonRequest(http.MethodGet, "/missing", nil, &map[string]any{}); err == nil {
|
|
t.Fatal("expected jsonRequest failure on 404")
|
|
}
|
|
|
|
origFactory := kubeClientFactory
|
|
kubeClientFactory = func() (*kubeClient, error) {
|
|
return client, nil
|
|
}
|
|
t.Cleanup(func() { kubeClientFactory = origFactory })
|
|
nodes := clusterNodes()
|
|
if len(nodes) != 2 || nodes[0].Name != "a" {
|
|
t.Fatalf("clusterNodes sort mismatch: %#v", nodes)
|
|
}
|
|
|
|
app := newTestApp(t)
|
|
app.settings.Namespace = "maintenance"
|
|
app.settings.RunnerImageARM64 = "runner:arm64"
|
|
state, err := app.remotePodState(client, "metis-build-test")
|
|
if err != nil {
|
|
t.Fatalf("remotePodState: %v", err)
|
|
}
|
|
if state.Reason != "Completed" || state.Message != "done" {
|
|
t.Fatalf("expected terminated state override, got %#v", state)
|
|
}
|
|
if _, err := app.remotePodLogs(client, "metis-build-test"); err == nil || !strings.Contains(err.Error(), "could not reach the node kubelet log endpoint") {
|
|
t.Fatalf("expected kubelet log endpoint error, got %v", err)
|
|
}
|
|
|
|
if _, err := app.runRemotePod("job-1", "metis-fail-test", map[string]any{}); err == nil {
|
|
t.Fatal("expected runRemotePod failure")
|
|
}
|
|
if _, err := app.ensureDevice("titan-22", "missing"); err == nil {
|
|
t.Fatal("expected ensureDevice missing target to fail")
|
|
}
|
|
}
|
|
|
|
func TestDeleteNodeObjectFallback(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
kubectl := filepath.Join(tmp, "kubectl")
|
|
if err := os.WriteFile(kubectl, []byte("#!/usr/bin/env bash\nset -eu\nprintf '%s' \"$*\" > \""+filepath.Join(tmp, "kubectl.args")+"\"\n"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv("PATH", tmp+string(os.PathListSeparator)+os.Getenv("PATH"))
|
|
origFactory := kubeClientFactory
|
|
kubeClientFactory = func() (*kubeClient, error) { return nil, errors.New("offline") }
|
|
t.Cleanup(func() { kubeClientFactory = origFactory })
|
|
if err := deleteNodeObject("titan-15"); err != nil {
|
|
t.Fatalf("deleteNodeObject fallback: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClusterActiveRemotePodLoadsCountsOnlyLivePods(t *testing.T) {
|
|
kube := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/namespaces/maintenance/pods":
|
|
if got := r.URL.Query().Get("labelSelector"); got != "app=metis-remote,metis-run=build" {
|
|
t.Fatalf("unexpected labelSelector %q", got)
|
|
}
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"items": []any{
|
|
map[string]any{
|
|
"metadata": map[string]any{"labels": map[string]string{"app": "metis-remote", "metis-run": "build"}},
|
|
"spec": map[string]any{"nodeName": "titan-04"},
|
|
"status": map[string]any{"phase": "Running"},
|
|
},
|
|
map[string]any{
|
|
"metadata": map[string]any{"labels": map[string]string{"app": "metis-remote", "metis-run": "build"}},
|
|
"spec": map[string]any{"nodeName": "titan-04"},
|
|
"status": map[string]any{"phase": "Pending"},
|
|
},
|
|
map[string]any{
|
|
"metadata": map[string]any{"labels": map[string]string{"app": "metis-remote", "metis-run": "build"}},
|
|
"spec": map[string]any{"nodeName": "titan-05"},
|
|
"status": map[string]any{"phase": "Succeeded"},
|
|
},
|
|
},
|
|
})
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}))
|
|
defer kube.Close()
|
|
|
|
origFactory := kubeClientFactory
|
|
kubeClientFactory = func() (*kubeClient, error) {
|
|
return kubeClientFactoryForURL(kube.URL, kube.Client()), nil
|
|
}
|
|
t.Cleanup(func() { kubeClientFactory = origFactory })
|
|
|
|
loads := clusterActiveRemotePodLoads("maintenance", "build")
|
|
if loads["titan-04"] != 2 {
|
|
t.Fatalf("expected titan-04 load 2, got %#v", loads)
|
|
}
|
|
if _, ok := loads["titan-05"]; ok {
|
|
t.Fatalf("expected succeeded pod to be ignored, got %#v", loads)
|
|
}
|
|
}
|