Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup API endpoints #764

Merged
merged 1 commit into from
Oct 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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