diff --git a/executor/linux/build_test.go b/executor/linux/build_test.go index ce5934d4..c1297a4a 100644 --- a/executor/linux/build_test.go +++ b/executor/linux/build_test.go @@ -1305,8 +1305,7 @@ func TestLinux_AssembleBuild(t *testing.T) { switch test.runtime { case constants.DriverKubernetes: - _pod := testPodFor(_pipeline) - _runtime, err = kubernetes.NewMock(_pod) + _runtime, err = kubernetes.NewMock(&v1.Pod{}) // do not use _pod here! AssembleBuild will load it. if err != nil { t.Errorf("unable to create kubernetes runtime engine: %v", err) } @@ -1337,6 +1336,30 @@ func TestLinux_AssembleBuild(t *testing.T) { t.Errorf("unable to create build: %v", err) } + // Kubernetes runtime needs to set up the Mock after CreateBuild is called + if test.runtime == constants.DriverKubernetes { + go func() { + _mockRuntime := _runtime.(kubernetes.MockKubernetesRuntime) + // This handles waiting until runtime.AssembleBuild has prepared the PodTracker. + _mockRuntime.WaitForPodTrackerReady() + // Normally, runtime.StreamBuild (which runs in a goroutine) calls PodTracker.Start. + _mockRuntime.StartPodTracker(context.Background()) + + _pod := testPodFor(_pipeline) + + // Now wait until the pod is created at the end of runtime.AssembleBuild. + _mockRuntime.WaitForPodCreate(_pod.GetNamespace(), _pod.GetName()) + + // Mark services running and secrets as completed, but no steps have started. + err := _mockRuntime.SimulateStatusUpdate(_pod, + testContainerStatuses(_pipeline, true, 0, 0), + ) + if err != nil { + t.Errorf("%s - failed to simulate pod update: %s", test.name, err) + } + }() + } + err = _engine.AssembleBuild(context.Background()) if test.failure { diff --git a/runtime/kubernetes/kubernetes.go b/runtime/kubernetes/kubernetes.go index d9cbdfcd..35dd9846 100644 --- a/runtime/kubernetes/kubernetes.go +++ b/runtime/kubernetes/kubernetes.go @@ -5,7 +5,6 @@ package kubernetes import ( - "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -13,6 +12,7 @@ import ( velav1alpha1 "github.com/go-vela/worker/runtime/kubernetes/apis/vela/v1alpha1" velaK8sClient "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned" + "github.com/sirupsen/logrus" ) type config struct { diff --git a/runtime/kubernetes/mock.go b/runtime/kubernetes/mock.go index 6c2ec36f..99666bcc 100644 --- a/runtime/kubernetes/mock.go +++ b/runtime/kubernetes/mock.go @@ -13,6 +13,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/tools/cache" velav1alpha1 "github.com/go-vela/worker/runtime/kubernetes/apis/vela/v1alpha1" fakeVelaK8sClient "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned/fake" @@ -99,6 +100,8 @@ type MockKubernetesRuntime interface { SetupMock() error MarkPodTrackerReady() StartPodTracker(context.Context) + WaitForPodTrackerReady() + WaitForPodCreate(string, string) SimulateResync(*v1.Pod) SimulateStatusUpdate(*v1.Pod, []v1.ContainerStatus) error } @@ -126,6 +129,47 @@ func (c *client) StartPodTracker(ctx context.Context) { c.PodTracker.Start(ctx) } +// WaitForPodTrackerReady waits for PodTracker.Ready to be closed (which happens in AssembleBuild). +// +// This function is intended for running tests only. +func (c *client) WaitForPodTrackerReady() { + <-c.PodTracker.Ready +} + +// WaitForPodCreate waits for PodTracker.Ready to be closed (which happens in AssembleBuild). +// +// This function is intended for running tests only. +func (c *client) WaitForPodCreate(namespace, name string) { + created := make(chan struct{}) + + c.PodTracker.podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + select { + case <-created: + // not interested in any other create events. + return + default: + break + } + + var ( + pod *v1.Pod + ok bool + ) + + if pod, ok = obj.(*v1.Pod); !ok { + return + } + + if pod.GetNamespace() == namespace && pod.GetName() == name { + close(created) + } + }, + }) + + <-created +} + // SimulateResync simulates an resync where the PodTracker refreshes its cache. // This resync is from oldPod to runtime.Pod. If nil, oldPod defaults to runtime.Pod. //