Skip to content

Commit

Permalink
Add Cluster Node Status and Configuration endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
dtomcej authored and traefiker committed Nov 6, 2019
1 parent 73a1a70 commit 2ccd002
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 8 deletions.
102 changes: 101 additions & 1 deletion internal/controller/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package controller
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"

"github.com/containous/maesh/internal/k8s"
"github.com/containous/traefik/v2/pkg/safe"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// API is an implementation of an api.
Expand All @@ -17,15 +20,25 @@ type API struct {
lastConfiguration *safe.Safe
apiPort int
deployLog *DeployLog
clients *k8s.ClientWrapper
meshNamespace string
}

type podInfo struct {
Name string
IP string
Ready bool
}

// NewAPI creates a new api.
func NewAPI(apiPort int, lastConfiguration *safe.Safe, deployLog *DeployLog) *API {
func NewAPI(apiPort int, lastConfiguration *safe.Safe, deployLog *DeployLog, clients *k8s.ClientWrapper, meshNamespace string) *API {
a := &API{
readiness: false,
lastConfiguration: lastConfiguration,
apiPort: apiPort,
deployLog: deployLog,
clients: clients,
meshNamespace: meshNamespace,
}

if err := a.Init(); err != nil {
Expand All @@ -42,6 +55,8 @@ func (a *API) Init() error {
a.router = mux.NewRouter()

a.router.HandleFunc("/api/configuration/current", a.getCurrentConfiguration)
a.router.HandleFunc("/api/status/nodes", a.getMeshNodes)
a.router.HandleFunc("/api/status/node/{node}/configuration", a.getMeshNodeConfiguration)
a.router.HandleFunc("/api/status/readiness", a.getReadiness)
a.router.HandleFunc("/api/log/deploylog", a.getDeployLog)

Expand Down Expand Up @@ -99,3 +114,88 @@ func (a *API) getDeployLog(w http.ResponseWriter, r *http.Request) {
log.Error(err)
}
}

// getMeshNodes returns a list of mesh nodes visible from the controller, and some basic readiness info.
func (a *API) getMeshNodes(w http.ResponseWriter, r *http.Request) {
podInfoList := []podInfo{}

podList, err := a.clients.ListPodWithOptions(a.meshNamespace, metav1.ListOptions{
LabelSelector: "component==maesh-mesh",
})
if err != nil {
writeErrorResponse(w, fmt.Sprintf("unable to retrieve pod list: %v", err), http.StatusInternalServerError)
return
}

for _, pod := range podList.Items {
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
}
}

p := podInfo{
Name: pod.Name,
IP: pod.Status.PodIP,
Ready: readiness,
}
podInfoList = append(podInfoList, p)
}

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

if err := json.NewEncoder(w).Encode(podInfoList); err != nil {
log.Error(err)
}
}

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

pod, exists, err := a.clients.GetPod(a.meshNamespace, vars["node"])
if err != nil {
writeErrorResponse(w, fmt.Sprintf("unable to retrieve pod: %v", err), http.StatusInternalServerError)
return
}

if !exists {
writeErrorResponse(w, fmt.Sprintf("unable to find pod: %s", vars["node"]), http.StatusNotFound)
return
}

resp, err := http.Get(fmt.Sprintf("http://%s:8080/api/rawdata", pod.Status.PodIP))
if err != nil {
writeErrorResponse(w, fmt.Sprintf("unable to get configuration from pod: %v", err), http.StatusBadGateway)
return
}

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
writeErrorResponse(w, fmt.Sprintf("unable to get configuration response body from pod: %v", err), http.StatusBadGateway)
return
}

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

if _, err := w.Write(body); err != nil {
log.Error(err)
}
}

func writeErrorResponse(w http.ResponseWriter, errorMessage string, status int) {
w.WriteHeader(status)
log.Error(errorMessage)

w.Header().Set("Content-Type", "text/plain; charset=us-ascii")

if _, err := w.Write([]byte(errorMessage)); err != nil {
log.Error(err)
}
}
12 changes: 6 additions & 6 deletions internal/controller/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

func TestEnableReadiness(t *testing.T) {
config := safe.Safe{}
api := NewAPI(9000, &config, nil)
api := NewAPI(9000, &config, nil, nil, "foo")

assert.Equal(t, false, api.readiness)

Expand Down Expand Up @@ -46,7 +46,7 @@ func TestGetReadiness(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
config := safe.Safe{}
api := NewAPI(9000, &config, nil)
api := NewAPI(9000, &config, nil, nil, "foo")
api.readiness = test.readiness

res := httptest.NewRecorder()
Expand All @@ -61,7 +61,7 @@ func TestGetReadiness(t *testing.T) {

func TestGetCurrentConfiguration(t *testing.T) {
config := safe.Safe{}
api := NewAPI(9000, &config, nil)
api := NewAPI(9000, &config, nil, nil, "foo")

config.Set("foo")

Expand All @@ -76,7 +76,7 @@ func TestGetCurrentConfiguration(t *testing.T) {
func TestGetDeployLog(t *testing.T) {
config := safe.Safe{}
log := NewDeployLog()
api := NewAPI(9000, &config, log)
api := NewAPI(9000, &config, log, nil, "foo")

currentTime := time.Now()
log.LogDeploy(currentTime, "foo", "bar", true, "blabla")
Expand All @@ -91,6 +91,6 @@ func TestGetDeployLog(t *testing.T) {
req := testhelpers.MustNewRequest(http.MethodGet, "/api/configuration/current", nil)

api.getDeployLog(res, req)
actual := res.Body.String()
assert.Equal(t, expected, actual)
assert.Equal(t, expected, res.Body.String())
assert.Equal(t, http.StatusOK, res.Code)
}
2 changes: 1 addition & 1 deletion internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (c *Controller) Init() error {
c.tcpStateTable = &k8s.State{Table: make(map[int]*k8s.ServiceWithPort)}

c.deployLog = NewDeployLog()
c.api = NewAPI(c.apiPort, &c.lastConfiguration, c.deployLog)
c.api = NewAPI(c.apiPort, &c.lastConfiguration, c.deployLog, c.clients, c.meshNamespace)

if c.smiEnabled {
c.provider = smi.New(c.clients, c.defaultMode, c.meshNamespace, c.tcpStateTable, c.ignored)
Expand Down

0 comments on commit 2ccd002

Please sign in to comment.