package buildlog import ( "fmt" "net/http" "strings" "testing" "time" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/rest" kclient "k8s.io/kubernetes/pkg/client" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" genericrest "k8s.io/kubernetes/pkg/registry/generic/rest" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/watch" "github.com/openshift/origin/pkg/build/api" "github.com/openshift/origin/pkg/build/registry/test" ) type testPodGetter struct{} func (p *testPodGetter) Get(ctx kapi.Context, name string) (runtime.Object, error) { pod := &kapi.Pod{} switch name { case "pending-build": pod = mockPod(kapi.PodPending) case "running-build": pod = mockPod(kapi.PodRunning) case "succeeded-build": pod = mockPod(kapi.PodSucceeded) case "failed-build": pod = mockPod(kapi.PodFailed) case "unknown-build": pod = mockPod(kapi.PodUnknown) } return pod, nil } // TestRegistryResourceLocation tests if proper resource location URL is returner // for different build states. // Note: For this test, the mocked pod is set to "Running" phase, so the test // is evaluating the outcome based only on build state. func TestRegistryResourceLocation(t *testing.T) { expectedLocations := map[api.BuildPhase]string{ api.BuildPhaseComplete: fmt.Sprintf("https://foo-host:12345/containerLogs/%s/running-build/foo-container", kapi.NamespaceDefault), api.BuildPhaseFailed: fmt.Sprintf("https://foo-host:12345/containerLogs/%s/running-build/foo-container", kapi.NamespaceDefault), api.BuildPhaseRunning: fmt.Sprintf("https://foo-host:12345/containerLogs/%s/running-build/foo-container", kapi.NamespaceDefault), api.BuildPhaseNew: "", api.BuildPhasePending: "", api.BuildPhaseError: "", api.BuildPhaseCancelled: "", } ctx := kapi.NewDefaultContext() for BuildPhase, expectedLocation := range expectedLocations { location, err := resourceLocationHelper(BuildPhase, "running", ctx) switch BuildPhase { case api.BuildPhaseError, api.BuildPhaseCancelled: if err == nil { t.Errorf("Expected error when Build is in %s state, got nothing", BuildPhase) } default: if err != nil { t.Errorf("Unexpected error: %v", err) } } if location != expectedLocation { t.Errorf("Status: %s Expected Location: %s, Got %s", BuildPhase, expectedLocation, location) } } } func TestWaitForBuild(t *testing.T) { ctx := kapi.NewDefaultContext() tests := []struct { name string status []api.BuildPhase expectError bool }{ { name: "New -> Running", status: []api.BuildPhase{api.BuildPhaseNew, api.BuildPhaseRunning}, expectError: false, }, { name: "New -> Pending -> Complete", status: []api.BuildPhase{api.BuildPhaseNew, api.BuildPhasePending, api.BuildPhaseComplete}, expectError: false, }, { name: "New -> Pending -> Failed", status: []api.BuildPhase{api.BuildPhaseNew, api.BuildPhasePending, api.BuildPhaseFailed}, expectError: false, }, { name: "New -> Pending -> Error", status: []api.BuildPhase{api.BuildPhaseNew, api.BuildPhasePending, api.BuildPhaseError}, expectError: true, }, { name: "Pending -> Cancelled", status: []api.BuildPhase{api.BuildPhasePending, api.BuildPhaseCancelled}, expectError: true, }, { name: "Error", status: []api.BuildPhase{api.BuildPhaseError}, expectError: true, }, } for _, tt := range tests { build := mockBuild(api.BuildPhasePending, "running") ch := make(chan watch.Event) registry := &buildRegistryWithWatch{ BuildRegistry: &test.BuildRegistry{}, Watch: &fakeWatch{ Channel: ch, }, } storage := REST{ BuildRegistry: registry, Timeout: defaultTimeout, } go func() { for _, status := range tt.status { ch <- watch.Event{ Type: watch.Modified, Object: mockBuild(status, "running"), } } }() err := storage.waitForBuild(ctx, build) if tt.expectError && err == nil { t.Errorf("%s: Expected an error but got nil from waitFromBuild", tt.name) } if !tt.expectError && err != nil { t.Errorf("%s: Unexpected error from watchBuild: %v", tt.name, err) } } } func TestWaitForBuildTimeout(t *testing.T) { ctx := kapi.NewDefaultContext() build := mockBuild(api.BuildPhasePending, "running") ch := make(chan watch.Event) registry := &buildRegistryWithWatch{ BuildRegistry: &test.BuildRegistry{}, Watch: &fakeWatch{ Channel: ch, }, } storage := REST{ BuildRegistry: registry, Timeout: 100 * time.Millisecond, } go func() { time.Sleep(500 * time.Millisecond) ch <- watch.Event{ Type: watch.Modified, Object: mockBuild(api.BuildPhaseRunning, "running"), } }() err := storage.waitForBuild(ctx, build) if err == nil || !strings.Contains(err.Error(), "timed out") { t.Errorf("Unexpected error result from waitForBuild: %v\n", err) } } type buildRegistryWithWatch struct { *test.BuildRegistry Watch watch.Interface } func (r *buildRegistryWithWatch) WatchBuilds(ctx kapi.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) { return r.Watch, r.Err } type fakeWatch struct { Channel chan watch.Event } func (w *fakeWatch) Stop() { close(w.Channel) } func (w *fakeWatch) ResultChan() <-chan watch.Event { return w.Channel } func resourceLocationHelper(BuildPhase api.BuildPhase, podPhase string, ctx kapi.Context) (string, error) { expectedBuild := mockBuild(BuildPhase, podPhase) buildRegistry := test.BuildRegistry{Build: expectedBuild} storage := REST{ BuildRegistry: &buildRegistry, PodGetter: &testPodGetter{}, ConnectionInfo: &kclient.HTTPKubeletClient{Config: &kclient.KubeletConfig{EnableHttps: true, Port: 12345}, Client: &http.Client{}}, Timeout: defaultTimeout, } getter := rest.GetterWithOptions(&storage) obj, err := getter.Get(ctx, "foo-build", &api.BuildLogOptions{NoWait: true}) if err != nil { return "", err } streamer, ok := obj.(*genericrest.LocationStreamer) if !ok { return "", fmt.Errorf("Result of get not LocationStreamer") } if streamer.Location != nil { return streamer.Location.String(), nil } return "", nil } func mockPod(podPhase kapi.PodPhase) *kapi.Pod { return &kapi.Pod{ ObjectMeta: kapi.ObjectMeta{ Name: "foo-pod", Namespace: kapi.NamespaceDefault, }, Spec: kapi.PodSpec{ Containers: []kapi.Container{ { Name: "foo-container", }, }, NodeName: "foo-host", }, Status: kapi.PodStatus{ Phase: podPhase, }, } } func mockBuild(status api.BuildPhase, podName string) *api.Build { return &api.Build{ ObjectMeta: kapi.ObjectMeta{ Name: podName, }, Status: api.BuildStatus{ Phase: status, }, } }