diff --git a/gateway/handlers/reader.go b/gateway/handlers/reader.go index 49ea60e9..46b3646e 100644 --- a/gateway/handlers/reader.go +++ b/gateway/handlers/reader.go @@ -6,7 +6,7 @@ package handlers import ( "context" "encoding/json" - "fmt" + "log" "net/http" "strings" @@ -19,7 +19,7 @@ import ( ) // MakeFunctionReader gives a summary of Function structs with Docker service stats overlaid with Prometheus counters. -func MakeFunctionReader(metricsOptions metrics.MetricOptions, c *client.Client) http.HandlerFunc { +func MakeFunctionReader(metricsOptions metrics.MetricOptions, c client.ServiceAPIClient) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { serviceFilter := filters.NewArgs() @@ -30,11 +30,14 @@ func MakeFunctionReader(metricsOptions metrics.MetricOptions, c *client.Client) services, err := c.ServiceList(context.Background(), options) if err != nil { - fmt.Println(err) + log.Println("Error getting service list:", err) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Error getting service list")) + return } // TODO: Filter only "faas" functions (via metadata?) - var functions []requests.Function + functions := []requests.Function{} for _, service := range services { diff --git a/gateway/tests/reader_test.go b/gateway/tests/reader_test.go new file mode 100644 index 00000000..7cc0ae69 --- /dev/null +++ b/gateway/tests/reader_test.go @@ -0,0 +1,208 @@ +package tests + +import ( + "encoding/json" + "errors" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" + "github.com/openfaas/faas/gateway/handlers" + "github.com/openfaas/faas/gateway/metrics" + "github.com/openfaas/faas/gateway/requests" + "golang.org/x/net/context" +) + +type testServiceApiClient struct { + serviceListServices []swarm.Service + serviceListError error +} + +func (c testServiceApiClient) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) { + return types.ServiceCreateResponse{}, nil +} + +func (c testServiceApiClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) { + return swarm.Service{}, []byte{}, nil +} + +func (c testServiceApiClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { + return c.serviceListServices, c.serviceListError +} + +func (c testServiceApiClient) ServiceRemove(ctx context.Context, serviceID string) error { + return nil +} + +func (c testServiceApiClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) { + return types.ServiceUpdateResponse{}, nil +} + +func (c testServiceApiClient) ServiceLogs(ctx context.Context, serviceID string, options types.ContainerLogsOptions) (io.ReadCloser, error) { + return nil, nil +} + +func (c testServiceApiClient) TaskLogs(ctx context.Context, taskID string, options types.ContainerLogsOptions) (io.ReadCloser, error) { + return nil, nil +} + +func (c testServiceApiClient) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) { + return swarm.Task{}, []byte{}, nil +} + +func (c testServiceApiClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) { + return []swarm.Task{}, nil +} + +func TestReaderSuccessReturnsOK(t *testing.T) { + m := metrics.MetricOptions{} + c := &testServiceApiClient{ + serviceListServices: []swarm.Service{}, + serviceListError: nil, + } + handler := handlers.MakeFunctionReader(m, c) + + w := httptest.NewRecorder() + r := &http.Request{} + handler.ServeHTTP(w, r) + + expected := http.StatusOK + if status := w.Code; status != expected { + t.Errorf("handler returned wrong status code: got %v want %v", + status, expected) + } +} + +func TestReaderSuccessReturnsJsonContent(t *testing.T) { + m := metrics.MetricOptions{} + c := &testServiceApiClient{ + serviceListServices: []swarm.Service{}, + serviceListError: nil, + } + handler := handlers.MakeFunctionReader(m, c) + + w := httptest.NewRecorder() + r := &http.Request{} + handler.ServeHTTP(w, r) + + expected := "application/json" + if contentType := w.Header().Get("Content-Type"); contentType != expected { + t.Errorf("content type header does not match: got %v want %v", + contentType, expected) + } +} + +func TestReaderSuccessReturnsCorrectBodyWithZeroFunctions(t *testing.T) { + m := metrics.MetricOptions{} + c := &testServiceApiClient{ + serviceListServices: []swarm.Service{}, + serviceListError: nil, + } + handler := handlers.MakeFunctionReader(m, c) + + w := httptest.NewRecorder() + r := &http.Request{} + handler.ServeHTTP(w, r) + + expected := "[]" + if w.Body.String() != expected { + t.Errorf("handler returned wrong body: got %v want %v", + w.Body.String(), expected) + } +} + +func TestReaderSuccessReturnsCorrectBodyWithOneFunction(t *testing.T) { + replicas := uint64(5) + services := []swarm.Service{ + swarm.Service{ + Spec: swarm.ServiceSpec{ + Mode: swarm.ServiceMode{ + Replicated: &swarm.ReplicatedService{ + Replicas: &replicas, + }, + }, + Annotations: swarm.Annotations{ + Name: "bar", + }, + TaskTemplate: swarm.TaskSpec{ + ContainerSpec: swarm.ContainerSpec{ + Env: []string{ + "fprocess=bar", + }, + Image: "foo/bar:latest", + Labels: map[string]string{ + "function": "bar", + }, + }, + }, + }, + }, + } + m := metrics.MetricOptions{} + c := &testServiceApiClient{ + serviceListServices: services, + serviceListError: nil, + } + handler := handlers.MakeFunctionReader(m, c) + + w := httptest.NewRecorder() + r := &http.Request{} + handler.ServeHTTP(w, r) + + functions := []requests.Function{ + requests.Function{ + Name: "bar", + Image: "foo/bar:latest", + InvocationCount: 0, + Replicas: 5, + EnvProcess: "bar", + }, + } + marshalled, _ := json.Marshal(functions) + expected := string(marshalled) + if w.Body.String() != expected { + t.Errorf("handler returned wrong body: got %v want %v", + w.Body.String(), expected) + } +} + +func TestReaderErrorReturnsInternalServerError(t *testing.T) { + m := metrics.MetricOptions{} + c := &testServiceApiClient{ + serviceListServices: nil, + serviceListError: errors.New("error"), + } + handler := handlers.MakeFunctionReader(m, c) + + w := httptest.NewRecorder() + r := &http.Request{} + handler.ServeHTTP(w, r) + + expected := http.StatusInternalServerError + if status := w.Code; status != expected { + t.Errorf("handler returned wrong status code: got %v want %v", + status, expected) + } +} + +func TestReaderErrorReturnsCorrectBody(t *testing.T) { + m := metrics.MetricOptions{} + c := &testServiceApiClient{ + serviceListServices: nil, + serviceListError: errors.New("error"), + } + handler := handlers.MakeFunctionReader(m, c) + + w := httptest.NewRecorder() + r := &http.Request{} + handler.ServeHTTP(w, r) + + expected := "Error getting service list" + if w.Body.String() != expected { + t.Errorf("handler returned wrong body: got %v want %v", + w.Body.String(), expected) + } +}