Skip to content

Commit

Permalink
Cleanup API endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
jspdown authored Oct 15, 2020
1 parent c7b0e8e commit 996dd9b
Show file tree
Hide file tree
Showing 9 changed files with 44 additions and 331 deletions.
5 changes: 1 addition & 4 deletions cmd/mesh/mesh.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,7 @@ func traefikMeshCommand(config *Configuration) error {

logger.Debugf("ACL mode enabled: %t", config.ACL)

apiServer, err := api.NewAPI(logger, config.APIPort, config.APIHost, clients.KubernetesClient(), config.Namespace)
if err != nil {
return fmt.Errorf("unable to create the API server: %w", err)
}
apiServer := api.NewAPI(logger, config.APIPort, config.APIHost, config.Namespace)

ctr := controller.NewMeshController(clients, controller.Config{
ACLEnabled: config.ACL,
Expand Down
15 changes: 6 additions & 9 deletions docs/content/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,22 @@ This can be useful when Traefik Mesh is not working as intended.
The API is accessed via the controller pod, and for security reasons is not exposed via service.
The API can be accessed by making a `GET` request to `http://<control pod IP>:9000` combined with one of the following paths:

## `/api/configuration/current`
## `/api/configuration`

This endpoint provides raw json of the current configuration built by the controller.

!!! Note
This may change on each request, as it is a live data structure.

## `/api/status/nodes`
## `/api/topology`

This endpoint provides a json array containing some details about the readiness of the Traefik Mesh nodes visible by the controller.
This endpoint will still return a 200 if there are no visible nodes.
This endpoint provides raw json of the current topology built by the controller.

## `/api/status/node/{traefik-mesh-pod-name}/configuration`
!!! Note
This may change on each request, as it is a live data structure.

This endpoint provides raw json of the current configuration on the Traefik Mesh node with the pod name given in `{traefik-mesh-pod-name}`.
This endpoint provides a 404 response if the pod cannot be found, or other non-200 status codes on other errors.
If errors are encountered, the error will be returned in the body, and logged on the controller.

## `/api/status/readiness`
## `/api/ready`

This endpoint returns a 200 response if the controller has successfully started.
Otherwise, it will return a 500.
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ spec:
containerPort: 9000
readinessProbe:
httpGet:
path: /api/status/readiness
path: /api/ready
port: api
initialDelaySeconds: 3
periodSeconds: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ spec:
containerPort: 9000
readinessProbe:
httpGet:
path: /api/status/readiness
path: /api/ready
port: api
initialDelaySeconds: 3
periodSeconds: 1
Expand Down
2 changes: 1 addition & 1 deletion integration/testdata/traefik-mesh/proxy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ spec:
- "--entryPoints.udp-15002.address=:15002/udp"
- "--entryPoints.udp-15003.address=:15003/udp"
- "--entryPoints.udp-15004.address=:15004/udp"
- "--providers.http.endpoint=http://traefik-mesh-controller.traefik-mesh.svc.cluster.local:9000/api/configuration/current"
- "--providers.http.endpoint=http://traefik-mesh-controller.traefik-mesh.svc.cluster.local:9000/api/configuration"
- "--providers.http.pollInterval=100ms"
- "--providers.http.pollTimeout=100ms"
- "--api.dashboard=false"
Expand Down
147 changes: 12 additions & 135 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
package api

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"

"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"github.com/traefik/mesh/v2/pkg/k8s"
"github.com/traefik/mesh/v2/pkg/provider"
"github.com/traefik/mesh/v2/pkg/safe"
"github.com/traefik/mesh/v2/pkg/topology"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
kubeerror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
listers "k8s.io/client-go/listers/core/v1"
)

// API is an implementation of an api.
Expand All @@ -32,37 +23,11 @@ type API struct {
topology *safe.Safe

namespace string
podLister listers.PodLister
logger logrus.FieldLogger
}

type podInfo struct {
Name string
IP string
Ready bool
}

// NewAPI creates a new api.
func NewAPI(logger logrus.FieldLogger, port int32, host string, client kubernetes.Interface, namespace string) (*API, error) {
informerFactory := informers.NewSharedInformerFactoryWithOptions(client, k8s.ResyncPeriod,
informers.WithNamespace(namespace),
informers.WithTweakListOptions(func(options *metav1.ListOptions) {
options.LabelSelector = k8s.ProxySelector().String()
}))

podLister := informerFactory.Core().V1().Pods().Lister()

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

informerFactory.Start(ctx.Done())

for t, ok := range informerFactory.WaitForCacheSync(ctx.Done()) {
if !ok {
return nil, fmt.Errorf("timed out while waiting for informer cache to sync: %s", t)
}
}

func NewAPI(logger logrus.FieldLogger, port int32, host, namespace string) *API {
router := mux.NewRouter()

api := &API{
Expand All @@ -75,18 +40,15 @@ func NewAPI(logger logrus.FieldLogger, port int32, host string, client kubernete
configuration: safe.New(provider.NewDefaultDynamicConfig()),
topology: safe.New(topology.NewTopology()),
readiness: safe.New(false),
podLister: podLister,
namespace: namespace,
logger: logger,
}

router.HandleFunc("/api/configuration/current", api.getCurrentConfiguration)
router.HandleFunc("/api/topology/current", api.getCurrentTopology)
router.HandleFunc("/api/status/nodes", api.getMeshNodes)
router.HandleFunc("/api/status/node/{node}/configuration", api.getMeshNodeConfiguration)
router.HandleFunc("/api/status/readiness", api.getReadiness)
router.HandleFunc("/api/configuration", api.getConfiguration)
router.HandleFunc("/api/topology", api.getTopology)
router.HandleFunc("/api/ready", api.getReadiness)

return api, nil
return api
}

// SetReadiness sets the readiness flag in the API.
Expand All @@ -95,8 +57,8 @@ func (a *API) SetReadiness(isReady bool) {
a.logger.Debugf("API readiness: %t", isReady)
}

// SetConfig sets the current dynamic configuration.
func (a *API) SetConfig(cfg *dynamic.Configuration) {
// SetConfiguration sets the current dynamic configuration.
func (a *API) SetConfiguration(cfg *dynamic.Configuration) {
a.configuration.Set(cfg)
}

Expand All @@ -105,18 +67,18 @@ func (a *API) SetTopology(topo *topology.Topology) {
a.topology.Set(topo)
}

// getCurrentConfiguration returns the current configuration.
func (a *API) getCurrentConfiguration(w http.ResponseWriter, _ *http.Request) {
// getConfiguration returns the current configuration.
func (a *API) getConfiguration(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")

if err := json.NewEncoder(w).Encode(a.configuration.Get()); err != nil {
a.logger.Errorf("Unable to serialize dynamic configuration: %v", err)
a.logger.Errorf("Unable to serialize configuration: %v", err)
http.Error(w, "", http.StatusInternalServerError)
}
}

// getCurrentTopology returns the current topology.
func (a *API) getCurrentTopology(w http.ResponseWriter, _ *http.Request) {
// getTopology returns the current topology.
func (a *API) getTopology(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")

if err := json.NewEncoder(w).Encode(a.topology.Get()); err != nil {
Expand All @@ -140,88 +102,3 @@ func (a *API) getReadiness(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "", http.StatusInternalServerError)
}
}

// getMeshNodes returns a list of mesh nodes visible from the controller, and some basic readiness info.
func (a *API) getMeshNodes(w http.ResponseWriter, _ *http.Request) {
podList, err := a.podLister.List(labels.Everything())
if err != nil {
a.logger.Errorf("Unable to retrieve pod list: %v", err)
http.Error(w, "", http.StatusInternalServerError)

return
}

podInfoList := make([]podInfo, len(podList))

for i, pod := range podList {
readiness := true

for _, status := range pod.Status.ContainerStatuses {
if !status.Ready {
// If there is a non-ready container, pod is not ready.
readiness = false
break
}
}

podInfoList[i] = podInfo{
Name: pod.Name,
IP: pod.Status.PodIP,
Ready: readiness,
}
}

w.Header().Set("Content-Type", "application/json")

if err := json.NewEncoder(w).Encode(podInfoList); err != nil {
a.logger.Errorf("Unable to serialize mesh nodes: %v", err)
http.Error(w, "", http.StatusInternalServerError)
}
}

// getMeshNodeConfiguration returns the configuration for a named pod.
func (a *API) getMeshNodeConfiguration(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)

pod, err := a.podLister.Pods(a.namespace).Get(vars["node"])
if err != nil {
if kubeerror.IsNotFound(err) {
http.Error(w, "", http.StatusNotFound)

return
}

http.Error(w, "", http.StatusInternalServerError)

return
}

resp, err := http.Get(fmt.Sprintf("http://%s:8080/api/rawdata", pod.Status.PodIP))
if err != nil {
a.logger.Errorf("Unable to get configuration from pod %q: %v", pod.Name, err)
http.Error(w, "", http.StatusBadGateway)

return
}

defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
a.logger.Errorf("Unable to close response body: %v", closeErr)
}
}()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
a.logger.Errorf("Unable to get configuration response body from pod %q: %v", pod.Name, err)
http.Error(w, "", http.StatusBadGateway)

return
}

w.Header().Set("Content-Type", "application/json")

if _, err := w.Write(body); err != nil {
a.logger.Errorf("Unable to write mesh nodes: %v", err)
http.Error(w, "", http.StatusInternalServerError)
}
}
Loading

0 comments on commit 996dd9b

Please sign in to comment.