Skip to content

Commit

Permalink
NSM Dashboard initial impl.
Browse files Browse the repository at this point in the history
Signed-off-by: Vitaliy Guschin <[email protected]>
  • Loading branch information
Vitaliy Guschin committed Dec 5, 2023
1 parent 1285ddf commit 1a2a41a
Show file tree
Hide file tree
Showing 8 changed files with 913 additions and 108 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@

.idea/
junit/
nohup.out
.DS_Store
.vscode
28 changes: 10 additions & 18 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
FROM golang:1.20.5-buster as go
ENV GO111MODULE=on
ENV CGO_ENABLED=0
ENV GOBIN=/bin
RUN go install github.com/go-delve/delve/cmd/[email protected]
ADD https://github.com/spiffe/spire/releases/download/v1.2.2/spire-1.2.2-linux-x86_64-glibc.tar.gz .
RUN tar xzvf spire-1.2.2-linux-x86_64-glibc.tar.gz -C /bin --strip=2 spire-1.2.2/bin/spire-server spire-1.2.2/bin/spire-agent
FROM golang:1.20.11

FROM go as build
WORKDIR /build
COPY . .
RUN go build -o /bin/app .
WORKDIR /backend
COPY dashboard-backend.go /backend
COPY model.go /backend
COPY go.mod /backend
COPY go.sum /backend

FROM build as test
CMD go test -test.v ./...
RUN go build -o dashboard-backend

FROM test as debug
CMD dlv -l :40000 --headless=true --api-version=2 test -test.v ./...
# Expose port for REST server
EXPOSE 3001

FROM alpine as runtime
COPY --from=build /bin/app /bin/app
CMD /bin/app
CMD ["./dashboard-backend"]
90 changes: 23 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,75 +1,31 @@
# Build
# Network Service Mesh Dashboard Backend

## Build cmd binary locally
NSM dashboard backend part providing graphical model data for [the UI part](https://github.com/networkservicemesh/cmd-dashboard-ui) through the REST API

You can build the locally by executing
Written in [Go](https://go.dev/)

```bash
go build ./...
```
The entire NSM dashboard deployment info see [here](https://github.com/networkservicemesh/deployments-k8s/tree/main/examples/observability/dashboard)

## Build Docker container
## Dev/debug

You can build the docker container by running:
### To run dashboard backend in the cluster:

```bash
docker build .
```
1. `git clone [email protected]:networkservicemesh/deployments-k8s.git`
2. `cd deployments-k8s/examples/observability/dashboard`
3. Edit `dashboard-pod.yaml` and remove the `dashboard-ui` container
4. `kubectl apply -f dashboard-pod.yaml`
5. `kubectl apply -f dashboard-backend-service.yaml`
6. `kubectl port-forward -n nsm-system service/dashboard-backend 3001:3001`
7. Check `http://localhost:3001/nodes` in the browser

# Testing
### To run dashboard backend with a custom container ([Docker](https://docs.docker.com/engine/install/) have to be installed) in the cluster:

## Testing Docker container

Testing is run via a Docker container. To run testing run:

```bash
docker run --privileged --rm $(docker build -q --target test .)
```

# Debugging

## Debugging the tests
If you wish to debug the test code itself, that can be acheived by running:

```bash
docker run --privileged --rm -p 40000:40000 $(docker build -q --target debug .)
```

This will result in the tests running under dlv. Connecting your debugger to localhost:40000 will allow you to debug.

```bash
-p 40000:40000
```
forwards port 40000 in the container to localhost:40000 where you can attach with your debugger.

```bash
--target debug
```

Runs the debug target, which is just like the test target, but starts tests with dlv listening on port 40000 inside the container.

## Debugging the cmd

When you run 'cmd' you will see an early line of output that tells you:

```Setting env variable DLV_LISTEN_FORWARDER to a valid dlv '--listen' value will cause the dlv debugger to execute this binary and listen as directed.```

If you follow those instructions when running the Docker container:
```bash
docker run --privileged -e DLV_LISTEN_FORWARDER=:50000 -p 50000:50000 --rm $(docker build -q --target test .)
```

```-e DLV_LISTEN_FORWARDER=:50000``` tells docker to set the environment variable DLV_LISTEN_FORWARDER to :50000 telling
dlv to listen on port 50000.

```-p 50000:50000``` tells docker to forward port 50000 in the container to port 50000 in the host. From there, you can
just connect dlv using your favorite IDE and debug cmd.

## Debugging the tests and the cmd

```bash
docker run --privileged -e DLV_LISTEN_FORWARDER=:50000 -p 40000:40000 -p 50000:50000 --rm $(docker build -q --target debug .)
```

Please note, the tests **start** the cmd, so until you connect to port 40000 with your debugger and walk the tests
through to the point of running cmd, you will not be able to attach a debugger on port 50000 to the cmd.
1. `git clone [email protected]:networkservicemesh/cmd-dashboard-backend.git`
2. `cd cmd-dashboard-backend`
3. Make the necessary code changes
4. Create a [Dockerhub](https://hub.docker.com/) repository
5. `docker build -t your-dh-namespace/dh-repo-name .`
6. `docker push your-dh-namespace/dh-repo-name`
7. `cd deployments-k8s/examples/observability/dashboard`
8. Edit `dashboard-pod.yaml` and set your Dockerhub image address for the `dashboard-backend` container
9. Execute the steps 3-7 from the previous section
173 changes: 173 additions & 0 deletions dashboard-backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package main

Check failure on line 1 in dashboard-backend.go

View workflow job for this annotation

GitHub Actions / golangci-lint / golangci-lint

Missed header for check (goheader)

import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"strings"
"time"

"github.com/networkservicemesh/api/pkg/api/networkservice"
"github.com/networkservicemesh/api/pkg/api/registry"

nested "github.com/antonfisher/nested-logrus-formatter"
"github.com/gorilla/mux"
"github.com/networkservicemesh/sdk/pkg/tools/log"
"github.com/networkservicemesh/sdk/pkg/tools/log/logruslogger"
"github.com/networkservicemesh/sdk/pkg/tools/spiffejwt"
"github.com/networkservicemesh/sdk/pkg/tools/token"
"github.com/networkservicemesh/sdk/pkg/tools/tracing"
"github.com/sirupsen/logrus"
"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
"github.com/spiffe/go-spiffe/v2/svid/x509svid"
"github.com/spiffe/go-spiffe/v2/workloadapi"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)

const REGISTRY_ADDR = "registry:5002"

func getNodesHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("GET /nodes requested")
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
err := json.NewEncoder(w).Encode(storageData.Nodes)
if err != nil {
handleError(w, err)
return
}
}

func getEdgesHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("GET /edges requested")
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
err := json.NewEncoder(w).Encode(storageData.Edges)
if err != nil {
handleError(w, err)
return
}
}

func handleError(w http.ResponseWriter, err error) {
fmt.Println("Error: ", err)
http.Error(w, "Error encoding JSON", http.StatusInternalServerError)
}

func main() {

Check failure on line 61 in dashboard-backend.go

View workflow job for this annotation

GitHub Actions / golangci-lint / golangci-lint

Function 'main' has too many statements (57 > 50) (funlen)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Read env variables
pollingIntervalSeconds := os.Getenv("POLLING_INTERVAL_SECONDS")
POLLING_INTERVAL_SECONDS, err := strconv.Atoi(pollingIntervalSeconds)
if err != nil {
POLLING_INTERVAL_SECONDS = 1
}

// Setup logger
log.EnableTracing(true)
logrus.SetFormatter(&nested.Formatter{})
ctx = log.WithLog(ctx, logruslogger.New(ctx, map[string]interface{}{"cmd": os.Args[:1]}))
logger := log.FromContext(ctx)

// Setup Spire
source, err := workloadapi.NewX509Source(ctx)
if err != nil {
logger.Fatalf("Error getting x509 source: %v", err.Error())
}
var svid *x509svid.SVID
svid, err = source.GetX509SVID()
if err != nil {
logger.Fatalf("Error getting x509 svid: %v", err.Error())
}
logger.Infof("sVID: %q", svid.ID)

tlsClientConfig := tlsconfig.MTLSClientConfig(source, source, tlsconfig.AuthorizeAny())
tlsClientConfig.MinVersion = tls.VersionTLS12

// Configure and run REST-server
r := mux.NewRouter()

r.HandleFunc("/nodes", getNodesHandler).Methods("GET")
r.HandleFunc("/edges", getEdgesHandler).Methods("GET")

http.Handle("/", r)
go func() {
if err := http.ListenAndServe(":3001", nil); err != nil {
logger.Fatalf("Error starting server: %v", err)
}
}()

// Create gRPC dial options
dialOptions := append(tracing.WithTracingDial(),
grpc.WithDefaultCallOptions(
grpc.WaitForReady(true),
grpc.PerRPCCredentials(token.NewPerRPCCredentials(spiffejwt.TokenGeneratorFunc(source, time.Duration(10)*time.Minute))),
),
grpc.WithTransportCredentials(credentials.NewTLS(tlsClientConfig)),
)

// Get nsmgrs info
conn, err := grpc.Dial(REGISTRY_ADDR, dialOptions...)
if err != nil {
logger.Fatalf("Failed dial to Registry: %v", err.Error())
}

client_nse := registry.NewNetworkServiceEndpointRegistryClient(conn)

stream_nse, err := client_nse.Find(context.Background(), &registry.NetworkServiceEndpointQuery{
NetworkServiceEndpoint: &registry.NetworkServiceEndpoint{
NetworkServiceNames: []string{"forwarder"},
},
})
if err != nil {
logger.Fatalf("Failed to perform Find NSE request: %v", err)
}

var nsmgrs []string
var nseList = registry.ReadNetworkServiceEndpointList(stream_nse)
for _, nse := range nseList {
nsmgr_addr := strings.Join(strings.Split(strings.TrimPrefix(nse.Url, "tcp://["), "]:"), ":")
logger.Infof("Extracted NSMGR addr: %q", nsmgr_addr)
nsmgrs = append(nsmgrs, nsmgr_addr)
}

// Monitor Connections
for {
connections := make(map[string]*networkservice.Connection)
for _, addr := range nsmgrs {
grpcConn, err := grpc.Dial(addr, dialOptions...)
if err != nil {
logger.Fatalf("Failed dial to NSMgr %q: %v", addr, err.Error())
}

client := networkservice.NewMonitorConnectionClient(grpcConn)

stream, err := client.MonitorConnections(context.Background(), &networkservice.MonitorScopeSelector{})
if err != nil {
logger.Fatalf("Error from MonitorConnectionClient: %v", err.Error())
}

event, err := stream.Recv()
if err != nil {
logger.Errorf("Error from monitorConnection stream: %v", err.Error())
} else {
logger.Infof("Received %d connections from %q", len(event.Connections), addr)
for _, connection := range event.Connections {
connectionId := generateConnectionId(connection)
if _, exists := connections[connectionId]; !exists {
connections[connectionId] = connection
}
}
}
}
logger.Infof("Parce connections. Size: %d", len(connections))
parceConnectionsToGraphicalModel(connections)
time.Sleep(time.Duration(POLLING_INTERVAL_SECONDS) * time.Second)
}
}
Loading

0 comments on commit 1a2a41a

Please sign in to comment.