Improve API endpoints
This commit is contained in:
parent
c8bf8e896a
commit
74c5ec70a9
20 changed files with 2266 additions and 1204 deletions
|
@ -111,6 +111,8 @@ All the following endpoints must be accessed with a `GET` HTTP request.
|
|||
| `/api/tcp/routers/{name}` | Returns the information of the TCP router specified by `name`. |
|
||||
| `/api/tcp/services` | Lists all the TCP services information. |
|
||||
| `/api/tcp/services/{name}` | Returns the information of the TCP service specified by `name`. |
|
||||
| `/api/entrypoints` | Lists all the entry points information. |
|
||||
| `/api/entrypoints/{name}` | Returns the information of the entry point specified by `name`. |
|
||||
| `/api/version` | Returns information about Traefik version. |
|
||||
| `/debug/vars` | See the [expvar](https://golang.org/pkg/expvar/) Go documentation. |
|
||||
| `/debug/pprof/` | See the [pprof Index](https://golang.org/pkg/net/http/pprof/#Index) Go documentation. |
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -38,37 +37,6 @@ type RunTimeRepresentation struct {
|
|||
TCPServices map[string]*dynamic.TCPServiceInfo `json:"tcpServices,omitempty"`
|
||||
}
|
||||
|
||||
type routerRepresentation struct {
|
||||
*dynamic.RouterInfo
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type serviceRepresentation struct {
|
||||
*dynamic.ServiceInfo
|
||||
ServerStatus map[string]string `json:"serverStatus,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type middlewareRepresentation struct {
|
||||
*dynamic.MiddlewareInfo
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type tcpRouterRepresentation struct {
|
||||
*dynamic.TCPRouterInfo
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type tcpServiceRepresentation struct {
|
||||
*dynamic.TCPServiceInfo
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type pageInfo struct {
|
||||
startIndex int
|
||||
endIndex int
|
||||
|
@ -81,6 +49,7 @@ type Handler struct {
|
|||
debug bool
|
||||
// runtimeConfiguration is the data set used to create all the data representations exposed by the API.
|
||||
runtimeConfiguration *dynamic.RuntimeConfiguration
|
||||
staticConfig static.Configuration
|
||||
statistics *types.Statistics
|
||||
// stats *thoasstats.Stats // FIXME stats
|
||||
// StatsRecorder *middlewares.StatsRecorder // FIXME stats
|
||||
|
@ -100,6 +69,7 @@ func New(staticConfig static.Configuration, runtimeConfig *dynamic.RuntimeConfig
|
|||
statistics: staticConfig.API.Statistics,
|
||||
dashboardAssets: staticConfig.API.DashboardAssets,
|
||||
runtimeConfiguration: rConfig,
|
||||
staticConfig: staticConfig,
|
||||
debug: staticConfig.API.Debug,
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +82,12 @@ func (h Handler) Append(router *mux.Router) {
|
|||
|
||||
router.Methods(http.MethodGet).Path("/api/rawdata").HandlerFunc(h.getRuntimeConfiguration)
|
||||
|
||||
// Experimental endpoint
|
||||
router.Methods(http.MethodGet).Path("/api/overview").HandlerFunc(h.getOverview)
|
||||
|
||||
router.Methods(http.MethodGet).Path("/api/entrypoints").HandlerFunc(h.getEntryPoints)
|
||||
router.Methods(http.MethodGet).Path("/api/entrypoints/{entryPointID}").HandlerFunc(h.getEntryPoint)
|
||||
|
||||
router.Methods(http.MethodGet).Path("/api/http/routers").HandlerFunc(h.getRouters)
|
||||
router.Methods(http.MethodGet).Path("/api/http/routers/{routerID}").HandlerFunc(h.getRouter)
|
||||
router.Methods(http.MethodGet).Path("/api/http/services").HandlerFunc(h.getServices)
|
||||
|
@ -135,283 +111,6 @@ func (h Handler) Append(router *mux.Router) {
|
|||
}
|
||||
}
|
||||
|
||||
func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]routerRepresentation, 0, len(h.runtimeConfiguration.Routers))
|
||||
|
||||
for name, rt := range h.runtimeConfiguration.Routers {
|
||||
results = append(results, routerRepresentation{
|
||||
RouterInfo: rt,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) {
|
||||
routerID := mux.Vars(request)["routerID"]
|
||||
|
||||
router, ok := h.runtimeConfiguration.Routers[routerID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := routerRepresentation{
|
||||
RouterInfo: router,
|
||||
Name: routerID,
|
||||
Provider: getProviderName(routerID),
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]serviceRepresentation, 0, len(h.runtimeConfiguration.Services))
|
||||
|
||||
for name, si := range h.runtimeConfiguration.Services {
|
||||
results = append(results, serviceRepresentation{
|
||||
ServiceInfo: si,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
ServerStatus: si.GetAllStatus(),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getService(rw http.ResponseWriter, request *http.Request) {
|
||||
serviceID := mux.Vars(request)["serviceID"]
|
||||
|
||||
service, ok := h.runtimeConfiguration.Services[serviceID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := serviceRepresentation{
|
||||
ServiceInfo: service,
|
||||
Name: serviceID,
|
||||
Provider: getProviderName(serviceID),
|
||||
ServerStatus: service.GetAllStatus(),
|
||||
}
|
||||
|
||||
rw.Header().Add("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]middlewareRepresentation, 0, len(h.runtimeConfiguration.Middlewares))
|
||||
|
||||
for name, mi := range h.runtimeConfiguration.Middlewares {
|
||||
results = append(results, middlewareRepresentation{
|
||||
MiddlewareInfo: mi,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getMiddleware(rw http.ResponseWriter, request *http.Request) {
|
||||
middlewareID := mux.Vars(request)["middlewareID"]
|
||||
|
||||
middleware, ok := h.runtimeConfiguration.Middlewares[middlewareID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := middlewareRepresentation{
|
||||
MiddlewareInfo: middleware,
|
||||
Name: middlewareID,
|
||||
Provider: getProviderName(middlewareID),
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]tcpRouterRepresentation, 0, len(h.runtimeConfiguration.TCPRouters))
|
||||
|
||||
for name, rt := range h.runtimeConfiguration.TCPRouters {
|
||||
results = append(results, tcpRouterRepresentation{
|
||||
TCPRouterInfo: rt,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) {
|
||||
routerID := mux.Vars(request)["routerID"]
|
||||
|
||||
router, ok := h.runtimeConfiguration.TCPRouters[routerID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := tcpRouterRepresentation{
|
||||
TCPRouterInfo: router,
|
||||
Name: routerID,
|
||||
Provider: getProviderName(routerID),
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]tcpServiceRepresentation, 0, len(h.runtimeConfiguration.TCPServices))
|
||||
|
||||
for name, si := range h.runtimeConfiguration.TCPServices {
|
||||
results = append(results, tcpServiceRepresentation{
|
||||
TCPServiceInfo: si,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) {
|
||||
serviceID := mux.Vars(request)["serviceID"]
|
||||
|
||||
service, ok := h.runtimeConfiguration.TCPServices[serviceID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := tcpServiceRepresentation{
|
||||
TCPServiceInfo: service,
|
||||
Name: serviceID,
|
||||
Provider: getProviderName(serviceID),
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.Request) {
|
||||
siRepr := make(map[string]*serviceInfoRepresentation, len(h.runtimeConfiguration.Services))
|
||||
for k, v := range h.runtimeConfiguration.Services {
|
||||
|
|
70
pkg/api/handler_entrypoint.go
Normal file
70
pkg/api/handler_entrypoint.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
)
|
||||
|
||||
type entryPointRepresentation struct {
|
||||
*static.EntryPoint
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func (h Handler) getEntryPoints(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]entryPointRepresentation, 0, len(h.staticConfig.EntryPoints))
|
||||
|
||||
for name, ep := range h.staticConfig.EntryPoints {
|
||||
results = append(results, entryPointRepresentation{
|
||||
EntryPoint: ep,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getEntryPoint(rw http.ResponseWriter, request *http.Request) {
|
||||
entryPointID := mux.Vars(request)["entryPointID"]
|
||||
|
||||
ep, ok := h.staticConfig.EntryPoints[entryPointID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := entryPointRepresentation{
|
||||
EntryPoint: ep,
|
||||
Name: entryPointID,
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
253
pkg/api/handler_entrypoint_test.go
Normal file
253
pkg/api/handler_entrypoint_test.go
Normal file
|
@ -0,0 +1,253 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHandler_EntryPoints(t *testing.T) {
|
||||
type expected struct {
|
||||
statusCode int
|
||||
nextPage string
|
||||
jsonFile string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
path string
|
||||
conf static.Configuration
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "all entry points, but no config",
|
||||
path: "/api/entrypoints",
|
||||
conf: static.Configuration{API: &static.API{}, Global: &static.Global{}},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/entrypoints-empty.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all entry points",
|
||||
path: "/api/entrypoints",
|
||||
conf: static.Configuration{
|
||||
Global: &static.Global{},
|
||||
API: &static.API{},
|
||||
EntryPoints: map[string]*static.EntryPoint{
|
||||
"web": {
|
||||
Address: ":80",
|
||||
Transport: &static.EntryPointsTransport{
|
||||
LifeCycle: &static.LifeCycle{
|
||||
RequestAcceptGraceTimeout: 1,
|
||||
GraceTimeOut: 2,
|
||||
},
|
||||
RespondingTimeouts: &static.RespondingTimeouts{
|
||||
ReadTimeout: 3,
|
||||
WriteTimeout: 4,
|
||||
IdleTimeout: 5,
|
||||
},
|
||||
},
|
||||
ProxyProtocol: &static.ProxyProtocol{
|
||||
Insecure: true,
|
||||
TrustedIPs: []string{"192.168.1.1", "192.168.1.2"},
|
||||
},
|
||||
ForwardedHeaders: &static.ForwardedHeaders{
|
||||
Insecure: true,
|
||||
TrustedIPs: []string{"192.168.1.3", "192.168.1.4"},
|
||||
},
|
||||
},
|
||||
"web-secure": {
|
||||
Address: ":443",
|
||||
Transport: &static.EntryPointsTransport{
|
||||
LifeCycle: &static.LifeCycle{
|
||||
RequestAcceptGraceTimeout: 10,
|
||||
GraceTimeOut: 20,
|
||||
},
|
||||
RespondingTimeouts: &static.RespondingTimeouts{
|
||||
ReadTimeout: 30,
|
||||
WriteTimeout: 40,
|
||||
IdleTimeout: 50,
|
||||
},
|
||||
},
|
||||
ProxyProtocol: &static.ProxyProtocol{
|
||||
Insecure: true,
|
||||
TrustedIPs: []string{"192.168.1.10", "192.168.1.20"},
|
||||
},
|
||||
ForwardedHeaders: &static.ForwardedHeaders{
|
||||
Insecure: true,
|
||||
TrustedIPs: []string{"192.168.1.30", "192.168.1.40"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/entrypoints.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all entry points, pagination, 1 res per page, want page 2",
|
||||
path: "/api/entrypoints?page=2&per_page=1",
|
||||
conf: static.Configuration{
|
||||
Global: &static.Global{},
|
||||
API: &static.API{},
|
||||
EntryPoints: map[string]*static.EntryPoint{
|
||||
"web1": {Address: ":81"},
|
||||
"web2": {Address: ":82"},
|
||||
"web3": {Address: ":83"},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "3",
|
||||
jsonFile: "testdata/entrypoints-page2.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all entry points, pagination, 19 results overall, 7 res per page, want page 3",
|
||||
path: "/api/entrypoints?page=3&per_page=7",
|
||||
conf: static.Configuration{
|
||||
Global: &static.Global{},
|
||||
API: &static.API{},
|
||||
EntryPoints: generateEntryPoints(19),
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/entrypoints-many-lastpage.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all entry points, pagination, 5 results overall, 10 res per page, want page 2",
|
||||
path: "/api/entrypoints?page=2&per_page=10",
|
||||
conf: static.Configuration{
|
||||
Global: &static.Global{},
|
||||
API: &static.API{},
|
||||
EntryPoints: generateEntryPoints(5),
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusBadRequest,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all entry points, pagination, 10 results overall, 10 res per page, want page 2",
|
||||
path: "/api/entrypoints?page=2&per_page=10",
|
||||
conf: static.Configuration{
|
||||
Global: &static.Global{},
|
||||
API: &static.API{},
|
||||
EntryPoints: generateEntryPoints(10),
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusBadRequest,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one entry point by id",
|
||||
path: "/api/entrypoints/bar",
|
||||
conf: static.Configuration{
|
||||
Global: &static.Global{},
|
||||
API: &static.API{},
|
||||
EntryPoints: map[string]*static.EntryPoint{
|
||||
"bar": {Address: ":81"},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/entrypoint-bar.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one entry point by id, that does not exist",
|
||||
path: "/api/entrypoints/foo",
|
||||
conf: static.Configuration{
|
||||
Global: &static.Global{},
|
||||
API: &static.API{},
|
||||
EntryPoints: map[string]*static.EntryPoint{
|
||||
"bar": {Address: ":81"},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one entry point by id, but no config",
|
||||
path: "/api/entrypoints/foo",
|
||||
conf: static.Configuration{API: &static.API{}, Global: &static.Global{}},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := New(test.conf, &dynamic.RuntimeConfiguration{})
|
||||
router := mux.NewRouter()
|
||||
handler.Append(router)
|
||||
|
||||
server := httptest.NewServer(router)
|
||||
|
||||
resp, err := http.DefaultClient.Get(server.URL + test.path)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, test.expected.statusCode, resp.StatusCode)
|
||||
|
||||
assert.Equal(t, test.expected.nextPage, resp.Header.Get(nextPageHeader))
|
||||
|
||||
if test.expected.jsonFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
if *updateExpected {
|
||||
var results interface{}
|
||||
err := json.Unmarshal(contents, &results)
|
||||
require.NoError(t, err)
|
||||
|
||||
newJSON, err := json.MarshalIndent(results, "", "\t")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ioutil.WriteFile(test.expected.jsonFile, newJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(test.expected.jsonFile)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, string(data), string(contents))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateEntryPoints(nb int) map[string]*static.EntryPoint {
|
||||
eps := make(map[string]*static.EntryPoint, nb)
|
||||
for i := 0; i < nb; i++ {
|
||||
eps[fmt.Sprintf("ep%2d", i)] = &static.EntryPoint{
|
||||
Address: ":" + strconv.Itoa(i),
|
||||
}
|
||||
}
|
||||
|
||||
return eps
|
||||
}
|
198
pkg/api/handler_http.go
Normal file
198
pkg/api/handler_http.go
Normal file
|
@ -0,0 +1,198 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
)
|
||||
|
||||
type routerRepresentation struct {
|
||||
*dynamic.RouterInfo
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type serviceRepresentation struct {
|
||||
*dynamic.ServiceInfo
|
||||
ServerStatus map[string]string `json:"serverStatus,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type middlewareRepresentation struct {
|
||||
*dynamic.MiddlewareInfo
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]routerRepresentation, 0, len(h.runtimeConfiguration.Routers))
|
||||
|
||||
for name, rt := range h.runtimeConfiguration.Routers {
|
||||
results = append(results, routerRepresentation{
|
||||
RouterInfo: rt,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) {
|
||||
routerID := mux.Vars(request)["routerID"]
|
||||
|
||||
router, ok := h.runtimeConfiguration.Routers[routerID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := routerRepresentation{
|
||||
RouterInfo: router,
|
||||
Name: routerID,
|
||||
Provider: getProviderName(routerID),
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]serviceRepresentation, 0, len(h.runtimeConfiguration.Services))
|
||||
|
||||
for name, si := range h.runtimeConfiguration.Services {
|
||||
results = append(results, serviceRepresentation{
|
||||
ServiceInfo: si,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
ServerStatus: si.GetAllStatus(),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getService(rw http.ResponseWriter, request *http.Request) {
|
||||
serviceID := mux.Vars(request)["serviceID"]
|
||||
|
||||
service, ok := h.runtimeConfiguration.Services[serviceID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := serviceRepresentation{
|
||||
ServiceInfo: service,
|
||||
Name: serviceID,
|
||||
Provider: getProviderName(serviceID),
|
||||
ServerStatus: service.GetAllStatus(),
|
||||
}
|
||||
|
||||
rw.Header().Add("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]middlewareRepresentation, 0, len(h.runtimeConfiguration.Middlewares))
|
||||
|
||||
for name, mi := range h.runtimeConfiguration.Middlewares {
|
||||
results = append(results, middlewareRepresentation{
|
||||
MiddlewareInfo: mi,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getMiddleware(rw http.ResponseWriter, request *http.Request) {
|
||||
middlewareID := mux.Vars(request)["middlewareID"]
|
||||
|
||||
middleware, ok := h.runtimeConfiguration.Middlewares[middlewareID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := middlewareRepresentation{
|
||||
MiddlewareInfo: middleware,
|
||||
Name: middlewareID,
|
||||
Provider: getProviderName(middlewareID),
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
575
pkg/api/handler_http_test.go
Normal file
575
pkg/api/handler_http_test.go
Normal file
|
@ -0,0 +1,575 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHandler_HTTP(t *testing.T) {
|
||||
type expected struct {
|
||||
statusCode int
|
||||
nextPage string
|
||||
jsonFile string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
path string
|
||||
conf dynamic.RuntimeConfiguration
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "all routers, but no config",
|
||||
path: "/api/http/routers",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/routers-empty.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all routers",
|
||||
path: "/api/http/routers",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: map[string]*dynamic.RouterInfo{
|
||||
"test@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar.other`)",
|
||||
Middlewares: []string{"addPrefixTest", "auth"},
|
||||
},
|
||||
},
|
||||
"bar@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/routers.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all routers, pagination, 1 res per page, want page 2",
|
||||
path: "/api/http/routers?page=2&per_page=1",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: map[string]*dynamic.RouterInfo{
|
||||
"bar@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||
},
|
||||
},
|
||||
"baz@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`toto.bar`)",
|
||||
},
|
||||
},
|
||||
"test@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar.other`)",
|
||||
Middlewares: []string{"addPrefixTest", "auth"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "3",
|
||||
jsonFile: "testdata/routers-page2.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all routers, pagination, 19 results overall, 7 res per page, want page 3",
|
||||
path: "/api/http/routers?page=3&per_page=7",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: generateHTTPRouters(19),
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/routers-many-lastpage.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all routers, pagination, 5 results overall, 10 res per page, want page 2",
|
||||
path: "/api/http/routers?page=2&per_page=10",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: generateHTTPRouters(5),
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusBadRequest,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all routers, pagination, 10 results overall, 10 res per page, want page 2",
|
||||
path: "/api/http/routers?page=2&per_page=10",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: generateHTTPRouters(10),
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusBadRequest,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one router by id",
|
||||
path: "/api/http/routers/bar@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: map[string]*dynamic.RouterInfo{
|
||||
"bar@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/router-bar.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one router by id, that does not exist",
|
||||
path: "/api/http/routers/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: map[string]*dynamic.RouterInfo{
|
||||
"bar@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one router by id, but no config",
|
||||
path: "/api/http/routers/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all services, but no config",
|
||||
path: "/api/http/services",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/services-empty.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all services",
|
||||
path: "/api/http/services",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Services: map[string]*dynamic.ServiceInfo{
|
||||
"bar@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.1", "UP")
|
||||
return si
|
||||
}(),
|
||||
"baz@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.2", "UP")
|
||||
return si
|
||||
}(),
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/services.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all services, 1 res per page, want page 2",
|
||||
path: "/api/http/services?page=2&per_page=1",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Services: map[string]*dynamic.ServiceInfo{
|
||||
"bar@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.1", "UP")
|
||||
return si
|
||||
}(),
|
||||
"baz@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.2", "UP")
|
||||
return si
|
||||
}(),
|
||||
"test@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.4", "UP")
|
||||
return si
|
||||
}(),
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "3",
|
||||
jsonFile: "testdata/services-page2.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one service by id",
|
||||
path: "/api/http/services/bar@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Services: map[string]*dynamic.ServiceInfo{
|
||||
"bar@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.1", "UP")
|
||||
return si
|
||||
}(),
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/service-bar.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one service by id, that does not exist",
|
||||
path: "/api/http/services/nono@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Services: map[string]*dynamic.ServiceInfo{
|
||||
"bar@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.1", "UP")
|
||||
return si
|
||||
}(),
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one service by id, but no config",
|
||||
path: "/api/http/services/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all middlewares, but no config",
|
||||
path: "/api/http/middlewares",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/middlewares-empty.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all middlewares",
|
||||
path: "/api/http/middlewares",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||
"auth@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
BasicAuth: &dynamic.BasicAuth{
|
||||
Users: []string{"admin:admin"},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
||||
},
|
||||
"addPrefixTest@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/titi",
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"test@myprovider"},
|
||||
},
|
||||
"addPrefixTest@anotherprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/toto",
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/middlewares.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all middlewares, 1 res per page, want page 2",
|
||||
path: "/api/http/middlewares?page=2&per_page=1",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||
"auth@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
BasicAuth: &dynamic.BasicAuth{
|
||||
Users: []string{"admin:admin"},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
||||
},
|
||||
"addPrefixTest@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/titi",
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"test@myprovider"},
|
||||
},
|
||||
"addPrefixTest@anotherprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/toto",
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "3",
|
||||
jsonFile: "testdata/middlewares-page2.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one middleware by id",
|
||||
path: "/api/http/middlewares/auth@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||
"auth@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
BasicAuth: &dynamic.BasicAuth{
|
||||
Users: []string{"admin:admin"},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
||||
},
|
||||
"addPrefixTest@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/titi",
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"test@myprovider"},
|
||||
},
|
||||
"addPrefixTest@anotherprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/toto",
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/middleware-auth.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one middleware by id, that does not exist",
|
||||
path: "/api/http/middlewares/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||
"auth@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
BasicAuth: &dynamic.BasicAuth{
|
||||
Users: []string{"admin:admin"},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one middleware by id, but no config",
|
||||
path: "/api/http/middlewares/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rtConf := &test.conf
|
||||
handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
|
||||
router := mux.NewRouter()
|
||||
handler.Append(router)
|
||||
|
||||
server := httptest.NewServer(router)
|
||||
|
||||
resp, err := http.DefaultClient.Get(server.URL + test.path)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, test.expected.statusCode, resp.StatusCode)
|
||||
|
||||
assert.Equal(t, test.expected.nextPage, resp.Header.Get(nextPageHeader))
|
||||
|
||||
if test.expected.jsonFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
if *updateExpected {
|
||||
var results interface{}
|
||||
err := json.Unmarshal(contents, &results)
|
||||
require.NoError(t, err)
|
||||
|
||||
newJSON, err := json.MarshalIndent(results, "", "\t")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ioutil.WriteFile(test.expected.jsonFile, newJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(test.expected.jsonFile)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, string(data), string(contents))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateHTTPRouters(nbRouters int) map[string]*dynamic.RouterInfo {
|
||||
routers := make(map[string]*dynamic.RouterInfo, nbRouters)
|
||||
for i := 0; i < nbRouters; i++ {
|
||||
routers[fmt.Sprintf("bar%2d@myprovider", i)] = &dynamic.RouterInfo{
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar" + strconv.Itoa(i) + "`)",
|
||||
},
|
||||
}
|
||||
}
|
||||
return routers
|
||||
}
|
200
pkg/api/handler_overview.go
Normal file
200
pkg/api/handler_overview.go
Normal file
|
@ -0,0 +1,200 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
)
|
||||
|
||||
type schemeOverview struct {
|
||||
Routers *section `json:"routers,omitempty"`
|
||||
Services *section `json:"services,omitempty"`
|
||||
Middlewares *section `json:"middlewares,omitempty"`
|
||||
}
|
||||
|
||||
type section struct {
|
||||
Total int `json:"total"`
|
||||
Warnings int `json:"warnings"`
|
||||
Errors int `json:"errors"`
|
||||
}
|
||||
|
||||
type features struct {
|
||||
Tracing string `json:"tracing"`
|
||||
Metrics string `json:"metrics"`
|
||||
AccessLog bool `json:"accessLog"`
|
||||
// TODO add certificates resolvers
|
||||
}
|
||||
|
||||
type overview struct {
|
||||
HTTP schemeOverview `json:"http"`
|
||||
TCP schemeOverview `json:"tcp"`
|
||||
Features features `json:"features,omitempty"`
|
||||
Providers []string `json:"providers,omitempty"`
|
||||
}
|
||||
|
||||
func (h Handler) getOverview(rw http.ResponseWriter, request *http.Request) {
|
||||
result := overview{
|
||||
HTTP: schemeOverview{
|
||||
Routers: getHTTPRouterSection(h.runtimeConfiguration.Routers),
|
||||
Services: getHTTPServiceSection(h.runtimeConfiguration.Services),
|
||||
Middlewares: getHTTPMiddlewareSection(h.runtimeConfiguration.Middlewares),
|
||||
},
|
||||
TCP: schemeOverview{
|
||||
Routers: getTCPRouterSection(h.runtimeConfiguration.TCPRouters),
|
||||
Services: getTCPServiceSection(h.runtimeConfiguration.TCPServices),
|
||||
},
|
||||
Features: getFeatures(h.staticConfig),
|
||||
Providers: getProviders(h.staticConfig),
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func getHTTPRouterSection(routers map[string]*dynamic.RouterInfo) *section {
|
||||
var countErrors int
|
||||
for _, rt := range routers {
|
||||
if rt.Err != "" {
|
||||
countErrors++
|
||||
}
|
||||
}
|
||||
|
||||
return §ion{
|
||||
Total: len(routers),
|
||||
Warnings: 0, // TODO
|
||||
Errors: countErrors,
|
||||
}
|
||||
}
|
||||
|
||||
func getHTTPServiceSection(services map[string]*dynamic.ServiceInfo) *section {
|
||||
var countErrors int
|
||||
for _, svc := range services {
|
||||
if svc.Err != nil {
|
||||
countErrors++
|
||||
}
|
||||
}
|
||||
|
||||
return §ion{
|
||||
Total: len(services),
|
||||
Warnings: 0, // TODO
|
||||
Errors: countErrors,
|
||||
}
|
||||
}
|
||||
|
||||
func getHTTPMiddlewareSection(middlewares map[string]*dynamic.MiddlewareInfo) *section {
|
||||
var countErrors int
|
||||
for _, md := range middlewares {
|
||||
if md.Err != nil {
|
||||
countErrors++
|
||||
}
|
||||
}
|
||||
|
||||
return §ion{
|
||||
Total: len(middlewares),
|
||||
Warnings: 0, // TODO
|
||||
Errors: countErrors,
|
||||
}
|
||||
}
|
||||
|
||||
func getTCPRouterSection(routers map[string]*dynamic.TCPRouterInfo) *section {
|
||||
var countErrors int
|
||||
for _, rt := range routers {
|
||||
if rt.Err != "" {
|
||||
countErrors++
|
||||
}
|
||||
}
|
||||
|
||||
return §ion{
|
||||
Total: len(routers),
|
||||
Warnings: 0, // TODO
|
||||
Errors: countErrors,
|
||||
}
|
||||
}
|
||||
|
||||
func getTCPServiceSection(services map[string]*dynamic.TCPServiceInfo) *section {
|
||||
var countErrors int
|
||||
for _, svc := range services {
|
||||
if svc.Err != nil {
|
||||
countErrors++
|
||||
}
|
||||
}
|
||||
|
||||
return §ion{
|
||||
Total: len(services),
|
||||
Warnings: 0, // TODO
|
||||
Errors: countErrors,
|
||||
}
|
||||
}
|
||||
|
||||
func getProviders(conf static.Configuration) []string {
|
||||
if conf.Providers == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var providers []string
|
||||
|
||||
v := reflect.ValueOf(conf.Providers).Elem()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
|
||||
if !field.IsNil() {
|
||||
providers = append(providers, v.Type().Field(i).Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
func getFeatures(conf static.Configuration) features {
|
||||
return features{
|
||||
Tracing: getTracing(conf),
|
||||
Metrics: getMetrics(conf),
|
||||
AccessLog: conf.AccessLog != nil,
|
||||
}
|
||||
}
|
||||
|
||||
func getMetrics(conf static.Configuration) string {
|
||||
if conf.Metrics == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(conf.Metrics).Elem()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
|
||||
if !field.IsNil() {
|
||||
return v.Type().Field(i).Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func getTracing(conf static.Configuration) string {
|
||||
if conf.Tracing == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(conf.Tracing).Elem()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
|
||||
if !field.IsNil() {
|
||||
return v.Type().Field(i).Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
230
pkg/api/handler_overview_test.go
Normal file
230
pkg/api/handler_overview_test.go
Normal file
|
@ -0,0 +1,230 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/containous/traefik/pkg/provider/docker"
|
||||
"github.com/containous/traefik/pkg/provider/file"
|
||||
"github.com/containous/traefik/pkg/provider/kubernetes/crd"
|
||||
"github.com/containous/traefik/pkg/provider/kubernetes/ingress"
|
||||
"github.com/containous/traefik/pkg/provider/marathon"
|
||||
"github.com/containous/traefik/pkg/provider/rancher"
|
||||
"github.com/containous/traefik/pkg/provider/rest"
|
||||
"github.com/containous/traefik/pkg/tracing/jaeger"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHandler_Overview(t *testing.T) {
|
||||
type expected struct {
|
||||
statusCode int
|
||||
jsonFile string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
path string
|
||||
confStatic static.Configuration
|
||||
confDyn dynamic.RuntimeConfiguration
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "without data in the dynamic configuration",
|
||||
path: "/api/overview",
|
||||
confStatic: static.Configuration{API: &static.API{}, Global: &static.Global{}},
|
||||
confDyn: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/overview-empty.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with data in the dynamic configuration",
|
||||
path: "/api/overview",
|
||||
confStatic: static.Configuration{API: &static.API{}, Global: &static.Global{}},
|
||||
confDyn: dynamic.RuntimeConfiguration{
|
||||
Services: map[string]*dynamic.ServiceInfo{
|
||||
"foo-service@myprovider": {
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||
"auth@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
BasicAuth: &dynamic.BasicAuth{
|
||||
Users: []string{"admin:admin"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"addPrefixTest@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/titi",
|
||||
},
|
||||
},
|
||||
},
|
||||
"addPrefixTest@anotherprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/toto",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Routers: map[string]*dynamic.RouterInfo{
|
||||
"bar@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||
},
|
||||
},
|
||||
"test@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar.other`)",
|
||||
Middlewares: []string{"addPrefixTest", "auth"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||
"tcpfoo-service@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||
"tcpbar@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "tcpfoo-service@myprovider",
|
||||
Rule: "HostSNI(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
"tcptest@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "tcpfoo-service@myprovider",
|
||||
Rule: "HostSNI(`foo.bar.other`)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/overview-dynamic.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with providers",
|
||||
path: "/api/overview",
|
||||
confStatic: static.Configuration{
|
||||
Global: &static.Global{},
|
||||
API: &static.API{},
|
||||
Providers: &static.Providers{
|
||||
Docker: &docker.Provider{},
|
||||
File: &file.Provider{},
|
||||
Marathon: &marathon.Provider{},
|
||||
KubernetesIngress: &ingress.Provider{},
|
||||
KubernetesCRD: &crd.Provider{},
|
||||
Rest: &rest.Provider{},
|
||||
Rancher: &rancher.Provider{},
|
||||
},
|
||||
},
|
||||
confDyn: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/overview-providers.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with features",
|
||||
path: "/api/overview",
|
||||
confStatic: static.Configuration{
|
||||
Global: &static.Global{},
|
||||
API: &static.API{},
|
||||
Metrics: &types.Metrics{
|
||||
Prometheus: &types.Prometheus{},
|
||||
},
|
||||
Tracing: &static.Tracing{
|
||||
Jaeger: &jaeger.Config{},
|
||||
},
|
||||
},
|
||||
confDyn: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/overview-features.json",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := New(test.confStatic, &test.confDyn)
|
||||
router := mux.NewRouter()
|
||||
handler.Append(router)
|
||||
|
||||
server := httptest.NewServer(router)
|
||||
|
||||
resp, err := http.DefaultClient.Get(server.URL + test.path)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, test.expected.statusCode, resp.StatusCode)
|
||||
|
||||
if test.expected.jsonFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
if *updateExpected {
|
||||
var results interface{}
|
||||
err := json.Unmarshal(contents, &results)
|
||||
require.NoError(t, err)
|
||||
|
||||
newJSON, err := json.MarshalIndent(results, "", "\t")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ioutil.WriteFile(test.expected.jsonFile, newJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(test.expected.jsonFile)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, string(data), string(contents))
|
||||
})
|
||||
}
|
||||
}
|
134
pkg/api/handler_tcp.go
Normal file
134
pkg/api/handler_tcp.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
)
|
||||
|
||||
type tcpRouterRepresentation struct {
|
||||
*dynamic.TCPRouterInfo
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type tcpServiceRepresentation struct {
|
||||
*dynamic.TCPServiceInfo
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]tcpRouterRepresentation, 0, len(h.runtimeConfiguration.TCPRouters))
|
||||
|
||||
for name, rt := range h.runtimeConfiguration.TCPRouters {
|
||||
results = append(results, tcpRouterRepresentation{
|
||||
TCPRouterInfo: rt,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) {
|
||||
routerID := mux.Vars(request)["routerID"]
|
||||
|
||||
router, ok := h.runtimeConfiguration.TCPRouters[routerID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := tcpRouterRepresentation{
|
||||
TCPRouterInfo: router,
|
||||
Name: routerID,
|
||||
Provider: getProviderName(routerID),
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]tcpServiceRepresentation, 0, len(h.runtimeConfiguration.TCPServices))
|
||||
|
||||
for name, si := range h.runtimeConfiguration.TCPServices {
|
||||
results = append(results, tcpServiceRepresentation{
|
||||
TCPServiceInfo: si,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) {
|
||||
serviceID := mux.Vars(request)["serviceID"]
|
||||
|
||||
service, ok := h.runtimeConfiguration.TCPServices[serviceID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := tcpServiceRepresentation{
|
||||
TCPServiceInfo: service,
|
||||
Name: serviceID,
|
||||
Provider: getProviderName(serviceID),
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
349
pkg/api/handler_tcp_test.go
Normal file
349
pkg/api/handler_tcp_test.go
Normal file
|
@ -0,0 +1,349 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHandler_TCP(t *testing.T) {
|
||||
type expected struct {
|
||||
statusCode int
|
||||
nextPage string
|
||||
jsonFile string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
path string
|
||||
conf dynamic.RuntimeConfiguration
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "all TCP routers, but no config",
|
||||
path: "/api/tcp/routers",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/tcprouters-empty.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all TCP routers",
|
||||
path: "/api/tcp/routers",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||
"test@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar.other`)",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/tcprouters.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all TCP routers, pagination, 1 res per page, want page 2",
|
||||
path: "/api/tcp/routers?page=2&per_page=1",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||
"bar@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
"baz@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`toto.bar`)",
|
||||
},
|
||||
},
|
||||
"test@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar.other`)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "3",
|
||||
jsonFile: "testdata/tcprouters-page2.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one TCP router by id",
|
||||
path: "/api/tcp/routers/bar@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||
"bar@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/tcprouter-bar.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one TCP router by id, that does not exist",
|
||||
path: "/api/tcp/routers/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||
"bar@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one TCP router by id, but no config",
|
||||
path: "/api/tcp/routers/bar@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all tcp services, but no config",
|
||||
path: "/api/tcp/services",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/tcpservices-empty.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all tcp services",
|
||||
path: "/api/tcp/services",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||
"bar@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.1:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
},
|
||||
"baz@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.2:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/tcpservices.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all tcp services, 1 res per page, want page 2",
|
||||
path: "/api/tcp/services?page=2&per_page=1",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||
"bar@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.1:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
},
|
||||
"baz@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.2:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider"},
|
||||
},
|
||||
"test@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.3:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "3",
|
||||
jsonFile: "testdata/tcpservices-page2.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one tcp service by id",
|
||||
path: "/api/tcp/services/bar@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||
"bar@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.1:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/tcpservice-bar.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one tcp service by id, that does not exist",
|
||||
path: "/api/tcp/services/nono@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||
"bar@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.1:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one tcp service by id, but no config",
|
||||
path: "/api/tcp/services/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rtConf := &test.conf
|
||||
handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
|
||||
router := mux.NewRouter()
|
||||
handler.Append(router)
|
||||
|
||||
server := httptest.NewServer(router)
|
||||
|
||||
resp, err := http.DefaultClient.Get(server.URL + test.path)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected.nextPage, resp.Header.Get(nextPageHeader))
|
||||
|
||||
require.Equal(t, test.expected.statusCode, resp.StatusCode)
|
||||
|
||||
if test.expected.jsonFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
if *updateExpected {
|
||||
var results interface{}
|
||||
err := json.Unmarshal(contents, &results)
|
||||
require.NoError(t, err)
|
||||
|
||||
newJSON, err := json.MarshalIndent(results, "", "\t")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ioutil.WriteFile(test.expected.jsonFile, newJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(test.expected.jsonFile)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, string(data), string(contents))
|
||||
})
|
||||
}
|
||||
}
|
|
@ -3,11 +3,9 @@ package api
|
|||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/mux"
|
||||
|
@ -19,885 +17,7 @@ import (
|
|||
|
||||
var updateExpected = flag.Bool("update_expected", false, "Update expected files in testdata")
|
||||
|
||||
func TestHandlerTCP_API(t *testing.T) {
|
||||
type expected struct {
|
||||
statusCode int
|
||||
nextPage string
|
||||
jsonFile string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
path string
|
||||
conf dynamic.RuntimeConfiguration
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "all TCP routers, but no config",
|
||||
path: "/api/tcp/routers",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/tcprouters-empty.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all TCP routers",
|
||||
path: "/api/tcp/routers",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||
"test@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar.other`)",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/tcprouters.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all TCP routers, pagination, 1 res per page, want page 2",
|
||||
path: "/api/tcp/routers?page=2&per_page=1",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||
"bar@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
"baz@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`toto.bar`)",
|
||||
},
|
||||
},
|
||||
"test@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar.other`)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "3",
|
||||
jsonFile: "testdata/tcprouters-page2.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one TCP router by id",
|
||||
path: "/api/tcp/routers/bar@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||
"bar@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/tcprouter-bar.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one TCP router by id, that does not exist",
|
||||
path: "/api/tcp/routers/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||
"bar@myprovider": {
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one TCP router by id, but no config",
|
||||
path: "/api/tcp/routers/bar@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all tcp services, but no config",
|
||||
path: "/api/tcp/services",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/tcpservices-empty.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all tcp services",
|
||||
path: "/api/tcp/services",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||
"bar@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.1:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
},
|
||||
"baz@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.2:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/tcpservices.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all tcp services, 1 res per page, want page 2",
|
||||
path: "/api/tcp/services?page=2&per_page=1",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||
"bar@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.1:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
},
|
||||
"baz@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.2:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider"},
|
||||
},
|
||||
"test@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.3:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "3",
|
||||
jsonFile: "testdata/tcpservices-page2.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one tcp service by id",
|
||||
path: "/api/tcp/services/bar@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||
"bar@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.1:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/tcpservice-bar.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one tcp service by id, that does not exist",
|
||||
path: "/api/tcp/services/nono@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||
"bar@myprovider": {
|
||||
TCPService: &dynamic.TCPService{
|
||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "127.0.0.1:2345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one tcp service by id, but no config",
|
||||
path: "/api/tcp/services/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rtConf := &test.conf
|
||||
handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
|
||||
router := mux.NewRouter()
|
||||
handler.Append(router)
|
||||
|
||||
server := httptest.NewServer(router)
|
||||
|
||||
resp, err := http.DefaultClient.Get(server.URL + test.path)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected.nextPage, resp.Header.Get(nextPageHeader))
|
||||
|
||||
require.Equal(t, test.expected.statusCode, resp.StatusCode)
|
||||
|
||||
if test.expected.jsonFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
if *updateExpected {
|
||||
var results interface{}
|
||||
err := json.Unmarshal(contents, &results)
|
||||
require.NoError(t, err)
|
||||
|
||||
newJSON, err := json.MarshalIndent(results, "", "\t")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ioutil.WriteFile(test.expected.jsonFile, newJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(test.expected.jsonFile)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, string(data), string(contents))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerHTTP_API(t *testing.T) {
|
||||
type expected struct {
|
||||
statusCode int
|
||||
nextPage string
|
||||
jsonFile string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
path string
|
||||
conf dynamic.RuntimeConfiguration
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "all routers, but no config",
|
||||
path: "/api/http/routers",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/routers-empty.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all routers",
|
||||
path: "/api/http/routers",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: map[string]*dynamic.RouterInfo{
|
||||
"test@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar.other`)",
|
||||
Middlewares: []string{"addPrefixTest", "auth"},
|
||||
},
|
||||
},
|
||||
"bar@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/routers.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all routers, pagination, 1 res per page, want page 2",
|
||||
path: "/api/http/routers?page=2&per_page=1",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: map[string]*dynamic.RouterInfo{
|
||||
"bar@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||
},
|
||||
},
|
||||
"baz@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`toto.bar`)",
|
||||
},
|
||||
},
|
||||
"test@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar.other`)",
|
||||
Middlewares: []string{"addPrefixTest", "auth"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "3",
|
||||
jsonFile: "testdata/routers-page2.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all routers, pagination, 19 results overall, 7 res per page, want page 3",
|
||||
path: "/api/http/routers?page=3&per_page=7",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: generateHTTPRouters(19),
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/routers-many-lastpage.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all routers, pagination, 5 results overall, 10 res per page, want page 2",
|
||||
path: "/api/http/routers?page=2&per_page=10",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: generateHTTPRouters(5),
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusBadRequest,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all routers, pagination, 10 results overall, 10 res per page, want page 2",
|
||||
path: "/api/http/routers?page=2&per_page=10",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: generateHTTPRouters(10),
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusBadRequest,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one router by id",
|
||||
path: "/api/http/routers/bar@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: map[string]*dynamic.RouterInfo{
|
||||
"bar@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/router-bar.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one router by id, that does not exist",
|
||||
path: "/api/http/routers/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Routers: map[string]*dynamic.RouterInfo{
|
||||
"bar@myprovider": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one router by id, but no config",
|
||||
path: "/api/http/routers/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all services, but no config",
|
||||
path: "/api/http/services",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/services-empty.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all services",
|
||||
path: "/api/http/services",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Services: map[string]*dynamic.ServiceInfo{
|
||||
"bar@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.1", "UP")
|
||||
return si
|
||||
}(),
|
||||
"baz@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.2", "UP")
|
||||
return si
|
||||
}(),
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/services.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all services, 1 res per page, want page 2",
|
||||
path: "/api/http/services?page=2&per_page=1",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Services: map[string]*dynamic.ServiceInfo{
|
||||
"bar@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.1", "UP")
|
||||
return si
|
||||
}(),
|
||||
"baz@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.2", "UP")
|
||||
return si
|
||||
}(),
|
||||
"test@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.4", "UP")
|
||||
return si
|
||||
}(),
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "3",
|
||||
jsonFile: "testdata/services-page2.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one service by id",
|
||||
path: "/api/http/services/bar@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Services: map[string]*dynamic.ServiceInfo{
|
||||
"bar@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.1", "UP")
|
||||
return si
|
||||
}(),
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/service-bar.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one service by id, that does not exist",
|
||||
path: "/api/http/services/nono@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Services: map[string]*dynamic.ServiceInfo{
|
||||
"bar@myprovider": func() *dynamic.ServiceInfo {
|
||||
si := &dynamic.ServiceInfo{
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||
}
|
||||
si.UpdateStatus("http://127.0.0.1", "UP")
|
||||
return si
|
||||
}(),
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one service by id, but no config",
|
||||
path: "/api/http/services/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all middlewares, but no config",
|
||||
path: "/api/http/middlewares",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/middlewares-empty.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all middlewares",
|
||||
path: "/api/http/middlewares",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||
"auth@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
BasicAuth: &dynamic.BasicAuth{
|
||||
Users: []string{"admin:admin"},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
||||
},
|
||||
"addPrefixTest@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/titi",
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"test@myprovider"},
|
||||
},
|
||||
"addPrefixTest@anotherprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/toto",
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "1",
|
||||
jsonFile: "testdata/middlewares.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all middlewares, 1 res per page, want page 2",
|
||||
path: "/api/http/middlewares?page=2&per_page=1",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||
"auth@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
BasicAuth: &dynamic.BasicAuth{
|
||||
Users: []string{"admin:admin"},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
||||
},
|
||||
"addPrefixTest@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/titi",
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"test@myprovider"},
|
||||
},
|
||||
"addPrefixTest@anotherprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/toto",
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
nextPage: "3",
|
||||
jsonFile: "testdata/middlewares-page2.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one middleware by id",
|
||||
path: "/api/http/middlewares/auth@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||
"auth@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
BasicAuth: &dynamic.BasicAuth{
|
||||
Users: []string{"admin:admin"},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
||||
},
|
||||
"addPrefixTest@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/titi",
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"test@myprovider"},
|
||||
},
|
||||
"addPrefixTest@anotherprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
AddPrefix: &dynamic.AddPrefix{
|
||||
Prefix: "/toto",
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusOK,
|
||||
jsonFile: "testdata/middleware-auth.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one middleware by id, that does not exist",
|
||||
path: "/api/http/middlewares/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{
|
||||
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||
"auth@myprovider": {
|
||||
Middleware: &dynamic.Middleware{
|
||||
BasicAuth: &dynamic.BasicAuth{
|
||||
Users: []string{"admin:admin"},
|
||||
},
|
||||
},
|
||||
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one middleware by id, but no config",
|
||||
path: "/api/http/middlewares/foo@myprovider",
|
||||
conf: dynamic.RuntimeConfiguration{},
|
||||
expected: expected{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rtConf := &test.conf
|
||||
handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
|
||||
router := mux.NewRouter()
|
||||
handler.Append(router)
|
||||
|
||||
server := httptest.NewServer(router)
|
||||
|
||||
resp, err := http.DefaultClient.Get(server.URL + test.path)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, test.expected.statusCode, resp.StatusCode)
|
||||
|
||||
assert.Equal(t, test.expected.nextPage, resp.Header.Get(nextPageHeader))
|
||||
|
||||
if test.expected.jsonFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
if *updateExpected {
|
||||
var results interface{}
|
||||
err := json.Unmarshal(contents, &results)
|
||||
require.NoError(t, err)
|
||||
|
||||
newJSON, err := json.MarshalIndent(results, "", "\t")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ioutil.WriteFile(test.expected.jsonFile, newJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(test.expected.jsonFile)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, string(data), string(contents))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandler_Configuration(t *testing.T) {
|
||||
func TestHandler_RawData(t *testing.T) {
|
||||
type expected struct {
|
||||
statusCode int
|
||||
json string
|
||||
|
@ -1053,17 +173,3 @@ func TestHandler_Configuration(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateHTTPRouters(nbRouters int) map[string]*dynamic.RouterInfo {
|
||||
routers := make(map[string]*dynamic.RouterInfo, nbRouters)
|
||||
for i := 0; i < nbRouters; i++ {
|
||||
routers[fmt.Sprintf("bar%2d@myprovider", i)] = &dynamic.RouterInfo{
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service@myprovider",
|
||||
Rule: "Host(`foo.bar" + strconv.Itoa(i) + "`)",
|
||||
},
|
||||
}
|
||||
}
|
||||
return routers
|
||||
}
|
||||
|
|
4
pkg/api/testdata/entrypoint-bar.json
vendored
Normal file
4
pkg/api/testdata/entrypoint-bar.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"address": ":81",
|
||||
"name": "bar"
|
||||
}
|
1
pkg/api/testdata/entrypoints-empty.json
vendored
Normal file
1
pkg/api/testdata/entrypoints-empty.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
[]
|
22
pkg/api/testdata/entrypoints-many-lastpage.json
vendored
Normal file
22
pkg/api/testdata/entrypoints-many-lastpage.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
[
|
||||
{
|
||||
"address": ":14",
|
||||
"name": "ep14"
|
||||
},
|
||||
{
|
||||
"address": ":15",
|
||||
"name": "ep15"
|
||||
},
|
||||
{
|
||||
"address": ":16",
|
||||
"name": "ep16"
|
||||
},
|
||||
{
|
||||
"address": ":17",
|
||||
"name": "ep17"
|
||||
},
|
||||
{
|
||||
"address": ":18",
|
||||
"name": "ep18"
|
||||
}
|
||||
]
|
6
pkg/api/testdata/entrypoints-page2.json
vendored
Normal file
6
pkg/api/testdata/entrypoints-page2.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
[
|
||||
{
|
||||
"address": ":82",
|
||||
"name": "web2"
|
||||
}
|
||||
]
|
60
pkg/api/testdata/entrypoints.json
vendored
Normal file
60
pkg/api/testdata/entrypoints.json
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
[
|
||||
{
|
||||
"address": ":80",
|
||||
"forwardedHeaders": {
|
||||
"insecure": true,
|
||||
"trustedIPs": [
|
||||
"192.168.1.3",
|
||||
"192.168.1.4"
|
||||
]
|
||||
},
|
||||
"name": "web",
|
||||
"proxyProtocol": {
|
||||
"insecure": true,
|
||||
"trustedIPs": [
|
||||
"192.168.1.1",
|
||||
"192.168.1.2"
|
||||
]
|
||||
},
|
||||
"transport": {
|
||||
"lifeCycle": {
|
||||
"graceTimeOut": 2,
|
||||
"requestAcceptGraceTimeout": 1
|
||||
},
|
||||
"respondingTimeouts": {
|
||||
"idleTimeout": 5,
|
||||
"readTimeout": 3,
|
||||
"writeTimeout": 4
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": ":443",
|
||||
"forwardedHeaders": {
|
||||
"insecure": true,
|
||||
"trustedIPs": [
|
||||
"192.168.1.30",
|
||||
"192.168.1.40"
|
||||
]
|
||||
},
|
||||
"name": "web-secure",
|
||||
"proxyProtocol": {
|
||||
"insecure": true,
|
||||
"trustedIPs": [
|
||||
"192.168.1.10",
|
||||
"192.168.1.20"
|
||||
]
|
||||
},
|
||||
"transport": {
|
||||
"lifeCycle": {
|
||||
"graceTimeOut": 20,
|
||||
"requestAcceptGraceTimeout": 10
|
||||
},
|
||||
"respondingTimeouts": {
|
||||
"idleTimeout": 50,
|
||||
"readTimeout": 30,
|
||||
"writeTimeout": 40
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
36
pkg/api/testdata/overview-dynamic.json
vendored
Normal file
36
pkg/api/testdata/overview-dynamic.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"features": {
|
||||
"accessLog": false,
|
||||
"metrics": "",
|
||||
"tracing": ""
|
||||
},
|
||||
"http": {
|
||||
"middlewares": {
|
||||
"errors": 0,
|
||||
"total": 3,
|
||||
"warnings": 0
|
||||
},
|
||||
"routers": {
|
||||
"errors": 0,
|
||||
"total": 2,
|
||||
"warnings": 0
|
||||
},
|
||||
"services": {
|
||||
"errors": 0,
|
||||
"total": 1,
|
||||
"warnings": 0
|
||||
}
|
||||
},
|
||||
"tcp": {
|
||||
"routers": {
|
||||
"errors": 0,
|
||||
"total": 2,
|
||||
"warnings": 0
|
||||
},
|
||||
"services": {
|
||||
"errors": 0,
|
||||
"total": 1,
|
||||
"warnings": 0
|
||||
}
|
||||
}
|
||||
}
|
36
pkg/api/testdata/overview-empty.json
vendored
Normal file
36
pkg/api/testdata/overview-empty.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"features": {
|
||||
"accessLog": false,
|
||||
"metrics": "",
|
||||
"tracing": ""
|
||||
},
|
||||
"http": {
|
||||
"middlewares": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
},
|
||||
"routers": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
},
|
||||
"services": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
}
|
||||
},
|
||||
"tcp": {
|
||||
"routers": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
},
|
||||
"services": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
}
|
||||
}
|
||||
}
|
36
pkg/api/testdata/overview-features.json
vendored
Normal file
36
pkg/api/testdata/overview-features.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"features": {
|
||||
"accessLog": false,
|
||||
"metrics": "Prometheus",
|
||||
"tracing": "Jaeger"
|
||||
},
|
||||
"http": {
|
||||
"middlewares": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
},
|
||||
"routers": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
},
|
||||
"services": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
}
|
||||
},
|
||||
"tcp": {
|
||||
"routers": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
},
|
||||
"services": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
}
|
||||
}
|
||||
}
|
45
pkg/api/testdata/overview-providers.json
vendored
Normal file
45
pkg/api/testdata/overview-providers.json
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"features": {
|
||||
"accessLog": false,
|
||||
"metrics": "",
|
||||
"tracing": ""
|
||||
},
|
||||
"http": {
|
||||
"middlewares": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
},
|
||||
"routers": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
},
|
||||
"services": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
}
|
||||
},
|
||||
"providers": [
|
||||
"Docker",
|
||||
"File",
|
||||
"Marathon",
|
||||
"KubernetesIngress",
|
||||
"KubernetesCRD",
|
||||
"Rest",
|
||||
"Rancher"
|
||||
],
|
||||
"tcp": {
|
||||
"routers": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
},
|
||||
"services": {
|
||||
"errors": 0,
|
||||
"total": 0,
|
||||
"warnings": 0
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue