fix(manager): add API test for healthz.

pull/363/head
Matt Butcher 9 years ago
parent 87d360afda
commit 05a3965602

@ -99,7 +99,7 @@ func registerRoutes(c *router.Context) router.Routes {
} }
func healthz(w http.ResponseWriter, r *http.Request, c *router.Context) error { func healthz(w http.ResponseWriter, r *http.Request, c *router.Context) error {
c.Log("manager: healthz checkpoint") log.Println("manager: healthz checkpoint")
// TODO: This should check the availability of the repository, and fail if it // TODO: This should check the availability of the repository, and fail if it
// cannot connect. // cannot connect.
fmt.Fprintln(w, "OK") fmt.Fprintln(w, "OK")

@ -0,0 +1,40 @@
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/kubernetes/deployment-manager/cmd/manager/router"
)
func TestHealthz(t *testing.T) {
c := mockContext()
s := httpHarness(c, "GET /", healthz)
defer s.Close()
res, err := http.Get(s.URL)
if err != nil {
t.Fatalf("Failed to GET healthz: %s", err)
} else if res.StatusCode != 200 {
t.Fatalf("Unexpected status: %d", res.StatusCode)
}
// TODO: Get the body and check on the content type and the body.
}
// httpHarness is a simple test server fixture.
// Simple fixture for standing up a test server with a single route.
//
// You must Close() the returned server.
func httpHarness(c *router.Context, route string, fn router.HandlerFunc) *httptest.Server {
r := router.NewRoutes()
r.Add(route, fn)
h := router.NewHandler(c, r)
return httptest.NewServer(h)
}
func mockContext() *router.Context {
// TODO: We need mocks for credentials and manager.
return &router.Context{}
}

@ -22,6 +22,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"log"
"net/http" "net/http"
"os" "os"
) )
@ -55,9 +56,9 @@ func main() {
routes := registerRoutes(c) routes := registerRoutes(c)
// Now create a server. // Now create a server.
c.Log("Starting Manager %s on %s", version.Version, c.Config.Address) log.Printf("Starting Manager %s on %s", version.Version, c.Config.Address)
if err := http.ListenAndServe(c.Config.Address, router.NewHandler(c, routes)); err != nil { if err := http.ListenAndServe(c.Config.Address, router.NewHandler(c, routes)); err != nil {
c.Err("Server exited with error %s", err) log.Printf("Server exited with error %s", err)
os.Exit(1) os.Exit(1)
} }
} }

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/* Package router is an HTTP router. /*Package router is an HTTP router.
This router provides appropriate dependency injection/encapsulation for the This router provides appropriate dependency injection/encapsulation for the
HTTP routing layer. This removes the requirement to set global variables for HTTP routing layer. This removes the requirement to set global variables for
@ -29,17 +29,15 @@ request and response.
package router package router
import ( import (
"fmt" "log"
"net/http" "net/http"
"os"
"github.com/Masterminds/httputil" "github.com/Masterminds/httputil"
"github.com/kubernetes/deployment-manager/cmd/manager/manager" "github.com/kubernetes/deployment-manager/cmd/manager/manager"
"github.com/kubernetes/deployment-manager/pkg/common" "github.com/kubernetes/deployment-manager/pkg/common"
helmhttp "github.com/kubernetes/deployment-manager/pkg/httputil"
) )
const LogAccess = "Access: %s %s"
// Config holds the global configuration parameters passed into the router. // Config holds the global configuration parameters passed into the router.
// //
// Config is used concurrently. Once a config is created, it should be treated // Config is used concurrently. Once a config is created, it should be treated
@ -80,36 +78,10 @@ type Context struct {
Config *Config Config *Config
// Manager is a deployment-manager/manager/manager.Manager // Manager is a deployment-manager/manager/manager.Manager
Manager manager.Manager Manager manager.Manager
Encoder Encoder Encoder helmhttp.Encoder
CredentialProvider common.CredentialProvider CredentialProvider common.CredentialProvider
} }
func (c *Context) Log(msg string, v ...interface{}) {
// FIXME: This should be configurable via the context.
fmt.Fprintf(os.Stdout, msg+"\n", v...)
}
func (c *Context) Err(msg string, v ...interface{}) {
// FIXME: This should be configurable via the context.
fmt.Fprintf(os.Stderr, msg+"\n", v...)
}
// NotFound writes a 404 error to the client and logs an error.
func NotFound(w http.ResponseWriter, r *http.Request) {
// TODO: Log this.
w.WriteHeader(http.StatusNotFound)
fmt.Fprintln(w, "File Not Found")
}
// Fatal writes a 500 response to the client and logs the message.
//
// Additional arguments are past into the the formatter as params to msg.
func Fatal(w http.ResponseWriter, r *http.Request, msg string, v ...interface{}) {
// TODO: Log this.
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintln(w, "Internal Server Error")
}
// HandlerFunc responds to an individual HTTP request. // HandlerFunc responds to an individual HTTP request.
// //
// Returned errors will be captured, logged, and returned as HTTP 500 errors. // Returned errors will be captured, logged, and returned as HTTP 500 errors.
@ -124,7 +96,7 @@ type Handler struct {
routes Routes routes Routes
} }
// Create a new Handler. // NewHandler creates a new Handler.
// //
// Routes cannot be modified after construction. The order that the route // Routes cannot be modified after construction. The order that the route
// names are returned by Routes.Paths() determines the lookup order. // names are returned by Routes.Paths() determines the lookup order.
@ -145,20 +117,20 @@ func NewHandler(c *Context, r Routes) *Handler {
// ServeHTTP serves an HTTP request. // ServeHTTP serves an HTTP request.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.c.Log(LogAccess, r.Method, r.URL) log.Printf(helmhttp.LogAccess, r.Method, r.URL)
route, err := h.resolver.Resolve(r) route, err := h.resolver.Resolve(r)
if err != nil { if err != nil {
NotFound(w, r) helmhttp.NotFound(w, r)
return return
} }
fn, ok := h.routes.Get(route) fn, ok := h.routes.Get(route)
if !ok { if !ok {
Fatal(w, r, "route %s missing", route) helmhttp.Fatal(w, r, "route %s missing", route)
} }
if err := fn(w, r, h.c); err != nil { if err := fn(w, r, h.c); err != nil {
Fatal(w, r, err.Error()) helmhttp.Fatal(w, r, err.Error())
} }
} }

2
glide.lock generated

@ -1,5 +1,5 @@
hash: f34e830b237fcba5202f84c55b21764226b7e8c201f7622e9639c7a628e33b0b hash: f34e830b237fcba5202f84c55b21764226b7e8c201f7622e9639c7a628e33b0b
updated: 2016-03-10T15:03:34.650809572-07:00 updated: 2016-03-14T10:53:18.091793732-06:00
imports: imports:
- name: github.com/aokoli/goutils - name: github.com/aokoli/goutils
version: 9c37978a95bd5c709a15883b6242714ea6709e64 version: 9c37978a95bd5c709a15883b6242714ea6709e64

@ -0,0 +1,21 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*Package httputil provides common HTTP tools.
This package provides tools for working with HTTP requests and responses.
*/
package httputil

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package router package httputil
import ( import (
"encoding/json" "encoding/json"
@ -28,26 +28,35 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
) )
// Encoder takes input and translate it to an expected encoded output.
//
// Implementations of encoders may use details of the HTTP request and response
// to correctly encode an object for return to the client.
//
// Encoders are expected to produce output, even if that output is an error
// message.
type Encoder interface { type Encoder interface {
// Encode encoders a given response // Encode encoders a given response
// //
// When an encoder fails, it logs any necessary data and then responds to // When an encoder fails, it logs any necessary data and then responds to
// the client. // the client.
Encode(http.ResponseWriter, *http.Request, *Context, interface{}) Encode(http.ResponseWriter, *http.Request, interface{})
} }
// AcceptEncodder uses the accept headers on a request to determine the response type. // AcceptEncoder uses the accept headers on a request to determine the response type.
// //
// It supports the following encodings: // It supports the following encodings:
// - application/json: passed to encoding/json.Marshal // - application/json: passed to encoding/json.Marshal
// - text/yaml: passed to gopkg.in/yaml.v2.Marshal // - text/yaml: passed to gopkg.in/yaml.v2.Marshal
// - text/plain: passed to fmt.Sprintf("%V") // - text/plain: passed to fmt.Sprintf("%V")
//
// It treats `application/x-yaml` as `text/yaml`.
type AcceptEncoder struct { type AcceptEncoder struct {
DefaultEncoding string DefaultEncoding string
} }
// Encode encodeds the given interface to the first available type in the Accept header. // Encode encodeds the given interface to the first available type in the Accept header.
func (e *AcceptEncoder) Encode(w http.ResponseWriter, r *http.Request, c *Context, out interface{}) { func (e *AcceptEncoder) Encode(w http.ResponseWriter, r *http.Request, out interface{}) {
a := r.Header.Get("accept") a := r.Header.Get("accept")
fn := encoders[e.DefaultEncoding] fn := encoders[e.DefaultEncoding]
mt := e.DefaultEncoding mt := e.DefaultEncoding
@ -82,6 +91,7 @@ func (e *AcceptEncoder) parseAccept(h string) (string, Marshaler) {
return e.DefaultEncoding, encoders[e.DefaultEncoding] return e.DefaultEncoding, encoders[e.DefaultEncoding]
} }
// Marshaler marshals an interface{} into a []byte.
type Marshaler func(interface{}) ([]byte, error) type Marshaler func(interface{}) ([]byte, error)
var encoders = map[string]Marshaler{ var encoders = map[string]Marshaler{
@ -91,6 +101,7 @@ var encoders = map[string]Marshaler{
"text/plain": textMarshal, "text/plain": textMarshal,
} }
// ErrUnsupportedKind indicates that the marshal cannot marshal a particular Go Kind (e.g. struct or chan).
var ErrUnsupportedKind = errors.New("unsupported kind") var ErrUnsupportedKind = errors.New("unsupported kind")
// textMarshal marshals v into a text representation ONLY IN NARROW CASES. // textMarshal marshals v into a text representation ONLY IN NARROW CASES.

@ -14,13 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package router package httputil
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest"
"testing" "testing"
) )
@ -72,14 +73,13 @@ func TestTextMarshal(t *testing.T) {
} }
func TestAcceptEncoder(t *testing.T) { func TestAcceptEncoder(t *testing.T) {
c := &Context{ enc := &AcceptEncoder{
Encoder: &AcceptEncoder{DefaultEncoding: "application/json"}, DefaultEncoding: "application/json",
} }
fn := func(w http.ResponseWriter, r *http.Request, c *Context) error { fn := func(w http.ResponseWriter, r *http.Request) {
c.Encoder.Encode(w, r, c, []string{"hello", "world"}) enc.Encode(w, r, []string{"hello", "world"})
return nil
} }
s := httpHarness(c, "GET /", fn) s := httptest.NewServer(http.HandlerFunc(fn))
defer s.Close() defer s.Close()
res, err := http.Get(s.URL) res, err := http.Get(s.URL)

@ -0,0 +1,48 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package httputil
import (
"fmt"
"log"
"net/http"
)
const (
// LogAccess is for logging access messages. Form: Access r.Method, r.URL
LogAccess = "Access: %s %s"
// LogNotFound is for logging 404 errors. Form: Not Found r.Method, r.URL
LogNotFound = "Not Found: %s %s"
// LogFatal is for logging 500 errors. Form: Internal Server Error r.Method r.URL message
LogFatal = "Internal Server Error: %s %s %s"
)
// NotFound writes a 404 error to the client and logs an error.
func NotFound(w http.ResponseWriter, r *http.Request) {
log.Printf(LogNotFound, r.Method, r.URL)
w.WriteHeader(http.StatusNotFound)
fmt.Fprintln(w, "File Not Found")
}
// Fatal writes a 500 response to the client and logs the message.
//
// Additional arguments are past into the the formatter as params to msg.
func Fatal(w http.ResponseWriter, r *http.Request, msg string, v ...interface{}) {
log.Printf(LogFatal, r.Method, r.URL, fmt.Sprintf(msg, v...))
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintln(w, "Internal Server Error")
}

@ -0,0 +1,50 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package httputil
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestNotFound(t *testing.T) {
fn := func(w http.ResponseWriter, r *http.Request) {
NotFound(w, r)
}
testStatusCode(http.HandlerFunc(fn), 404, t)
}
func TestFatal(t *testing.T) {
fn := func(w http.ResponseWriter, r *http.Request) {
Fatal(w, r, "fatal %s", "foo")
}
testStatusCode(http.HandlerFunc(fn), 500, t)
}
func testStatusCode(fn http.HandlerFunc, expect int, t *testing.T) {
s := httptest.NewServer(fn)
defer s.Close()
res, err := http.Get(s.URL)
if err != nil {
t.Fatal(err)
}
if res.StatusCode != expect {
t.Errorf("Expected %d, got %d", expect, res.StatusCode)
}
}
Loading…
Cancel
Save