diff --git a/build/ci/.documentation-ci.yml b/build/ci/.documentation-ci.yml index 23a5caf7f43f0601939a1740e3ca3803d37ae007..76e5303f163a2a7f5e0206dba6bfe2d938218433 100644 --- a/build/ci/.documentation-ci.yml +++ b/build/ci/.documentation-ci.yml @@ -11,7 +11,7 @@ documentation:pdf: - documentation/design/*.md script: - cd documentation/design - - pandoc --filter pandoc-citeproc --bibliography=bibliography.bib --csl=acm-sig-proceedings.csl + - pandoc --citeproc --bibliography=bibliography.bib --csl=acm-sig-proceedings.csl --variable papersize=a4paper -s *.md -o documentation.pdf artifacts: paths: diff --git a/cmd/gosdn-tview/app/app.go b/cmd/gosdn-tview/app/app.go index 037c1adc771ed4a092241a9930b5bb0ba30e4279..3570285c4b481c7608b6490c345cf3d9c20dc2cb 100644 --- a/cmd/gosdn-tview/app/app.go +++ b/cmd/gosdn-tview/app/app.go @@ -1,5 +1,6 @@ package app +//TODO: App should be a Singleton i guess import ( "github.com/rivo/tview" ) @@ -56,6 +57,11 @@ func (a *App) Draw() { a.app.Draw() } +//QueueUpdateDraw calls tview.QueueUpdateDraw() +func (a *App) QueueUpdateDraw(f func()) { + a.app.QueueUpdateDraw(f) +} + //SetFocus sets the focus on new tview.Primitive func (a *App) SetFocus(v tview.Primitive) { a.app.SetFocus(v) diff --git a/cmd/gosdn-tview/grpc/commands.go b/cmd/gosdn-tview/grpc/commands.go index 5b0c0790601164401d30bd6a39d86377627e355a..10ba4652b2016af91c839b42f0ef07d7a2b4acbe 100644 --- a/cmd/gosdn-tview/grpc/commands.go +++ b/cmd/gosdn-tview/grpc/commands.go @@ -1,13 +1,17 @@ package commands import ( - pb "code.fbi.h-da.de/cocsn/gosdn/api/proto" - "code.fbi.h-da.de/cocsn/gosdn/log" "context" + "time" + + pb "code.fbi.h-da.de/cocsn/gosdn/api/proto" + "code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/app" "github.com/rivo/tview" grpc "google.golang.org/grpc" + //Package google.golang.org/grpc/health is needed to make gRPC Health Check work + _ "google.golang.org/grpc/health" + healthpb "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/protobuf/types/known/emptypb" - "time" ) const ( @@ -30,18 +34,32 @@ var CommandList = []command{ {"shutdown", "request controller to shutdown", goSDNShutdown}, } +var serviceConfig = `{ + "loadBalancingPolicy": "round_robin", + "healthCheckConfig": { + "serviceName": "" + } +}` + //Connect creates a new connection to the gRPC server func Connect(address string) (*grpc.ClientConn, error) { - return grpc.Dial(address, grpc.WithInsecure(), grpc.WithTimeout(5*time.Second), grpc.WithBlock()) + options := []grpc.DialOption{ + grpc.WithInsecure(), + grpc.WithBlock(), + grpc.WithDefaultServiceConfig(serviceConfig), + grpc.WithTimeout(5 * time.Second), + } + + return grpc.Dial(address, options...) } //GoSDNLogStream creates a continuous gRPC stream to recieve goSDN logs -func GoSDNLogStream(conn *grpc.ClientConn, clv *tview.TextView) error { +func GoSDNLogStream(app *app.App, conn *grpc.ClientConn, tv *tview.TextView) error { var streamError error c := pb.NewGrpcCliClient(conn) stream, err := c.CreateLogStream(context.Background(), &emptypb.Empty{}) if err != nil { - log.Error(err) + return err } go func(stream pb.GrpcCli_CreateLogStreamClient) { @@ -49,10 +67,23 @@ func GoSDNLogStream(conn *grpc.ClientConn, clv *tview.TextView) error { msg, err := stream.Recv() if err != nil { streamError = err + + go func() { + ticker := time.NewTicker(5 * time.Second) + for range ticker.C { + if err := GoSDNLogStream(app, conn, tv); err == nil { + ticker.Stop() + return + } + } + }() + break } response := []byte(msg.Log) - clv.Write(response) + app.QueueUpdateDraw(func() { + tv.Write(response) + }) } }(stream) @@ -68,11 +99,55 @@ func goSDNSayHello(conn *grpc.ClientConn) string { defer cancel() r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name}) if err != nil { - log.Fatal(err) + return err.Error() } + return r.GetMessage() } +//WatchHealth continuous gRPC Health Check stream to recieve health changes +func WatchHealth(service string, app *app.App, conn *grpc.ClientConn, tv *tview.TextView) error { + var streamError error + + c := healthpb.NewHealthClient(conn) + stream, err := c.Watch(context.Background(), &healthpb.HealthCheckRequest{Service: service}) + if err != nil { + app.QueueUpdateDraw(func() { + tv.Clear() + tv.SetText(err.Error()) + }) + return err + } + + go func(stream healthpb.Health_WatchClient) { + for { + msg, err := stream.Recv() + if err != nil { + streamError = err + go func() { + ticker := time.NewTicker(5 * time.Second) + for range ticker.C { + if err := WatchHealth(service, app, conn, tv); err == nil { + ticker.Stop() + return + } + } + }() + + break + } + + app.QueueUpdateDraw(func() { + tv.Clear() + tv.SetText(msg.GetStatus().String()) + }) + + } + }(stream) + + return streamError +} + func goSDNShutdown(conn *grpc.ClientConn) string { c := pb.NewGrpcCliClient(conn) @@ -83,7 +158,7 @@ func goSDNShutdown(conn *grpc.ClientConn) string { defer cancel() r, err := c.Shutdown(ctx, &pb.ShutdownRequest{Name: name}) if err != nil { - log.Fatal(err) + return err.Error() } return r.GetMessage() } @@ -105,7 +180,7 @@ func TAPIGetEdge(conn *grpc.ClientConn) string { defer cancel() r, err := c.TAPIGetEdge(ctx, &pb.TAPIRequest{Name: name}) if err != nil { - log.Fatal(err) + return err.Error() } return r.GetMessage() } @@ -121,7 +196,7 @@ func TAPIGetEdgeNode(conn *grpc.ClientConn) string { defer cancel() r, err := c.TAPIGetEdgeNode(ctx, &pb.TAPIRequest{Name: name}) if err != nil { - log.Fatal(err) + return err.Error() } return r.GetMessage() } @@ -138,7 +213,7 @@ func TAPIGetLink(conn *grpc.ClientConn) string { defer cancel() r, err := c.TAPIGetLink(ctx, &pb.TAPIRequest{Name: name}) if err != nil { - log.Fatal(err) + return err.Error() } return r.GetMessage() } diff --git a/cmd/gosdn-tview/main.go b/cmd/gosdn-tview/main.go index 4191524b856526f9d6d13cd2fb3db654127a80f9..0013e46c2d91864c3d9b39fc4772d7b521247fff 100644 --- a/cmd/gosdn-tview/main.go +++ b/cmd/gosdn-tview/main.go @@ -18,7 +18,6 @@ func main() { addr := strings.Join([]string{*addrIPv4, strconv.Itoa(*port)}, ":") conn, err := grpc.Connect(addr) - if err != nil { log.Fatal(err) } diff --git a/cmd/gosdn-tview/views/consoleLogView.go b/cmd/gosdn-tview/views/consoleLogView.go index 0c7769d2ae61d6e4fb1a5dc15fce4832fd025e95..35af59cdad223200f66ce041c3f406305d4a9bfa 100644 --- a/cmd/gosdn-tview/views/consoleLogView.go +++ b/cmd/gosdn-tview/views/consoleLogView.go @@ -1,6 +1,7 @@ package views import ( + "code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/app" commands "code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/grpc" "github.com/rivo/tview" "google.golang.org/grpc" @@ -13,7 +14,7 @@ type ConsoleLogView struct { } //NewConsoleLogView creates a new ConsoleLogView -func NewConsoleLogView(title string, conn *grpc.ClientConn) *ConsoleLogView { +func NewConsoleLogView(title string, app *app.App, conn *grpc.ClientConn) *ConsoleLogView { clv := &ConsoleLogView{ consoleLogView: tview.NewTextView(), title: title, @@ -26,7 +27,7 @@ func NewConsoleLogView(title string, conn *grpc.ClientConn) *ConsoleLogView { SetBorder(true). SetTitle(clv.title) - commands.GoSDNLogStream(conn, clv.consoleLogView) + commands.GoSDNLogStream(app, conn, clv.consoleLogView) return clv } diff --git a/cmd/gosdn-tview/views/gRPCStatusView.go b/cmd/gosdn-tview/views/gRPCStatusView.go index 836c06a167173fe23f283a6dcfc04edfb7dd47b0..a4d9df9e65f9908b7e21ddc5fdd20ece2da34982 100644 --- a/cmd/gosdn-tview/views/gRPCStatusView.go +++ b/cmd/gosdn-tview/views/gRPCStatusView.go @@ -2,9 +2,9 @@ package views import ( "code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/app" + commands "code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/grpc" "github.com/rivo/tview" "google.golang.org/grpc" - "time" ) //GRPCStatusView is an application view to display the current status of @@ -26,13 +26,7 @@ func NewGRPCStatusView(app *app.App, conn *grpc.ClientConn) *GRPCStatusView { SetBorder(true). SetTitle("gRPC") - //TODO: gRPCs Health Check looks like a way better alternative here! - //--> no app.Draw() required anymore - sv.gRPCStatusView.SetChangedFunc(func() { - app.Draw() - }) - - go gRPCTicker(sv, conn) + commands.WatchHealth("", app, conn, sv.gRPCStatusView) return sv } @@ -47,19 +41,3 @@ func (sv *GRPCStatusView) SetContent(s string) { sv.gRPCStatusView.Clear() sv.gRPCStatusView.SetText(s) } - -func gRPCTicker(sv *GRPCStatusView, conn *grpc.ClientConn) { - sv.SetContent(checkStatus(conn)) - //TODO: refactor -> get rid of hardcoded values - ticker := time.NewTicker(5 * time.Second) - for range ticker.C { - sv.SetContent(checkStatus(conn)) - } -} - -func checkStatus(conn *grpc.ClientConn) string { - if str := conn.GetState().String(); str == "READY" || str == "IDLE" { - return "[green]" + "connected" - } - return "[red]" + "disconnected" -} diff --git a/cmd/gosdn-tview/views/resultAndInputView.go b/cmd/gosdn-tview/views/resultAndInputView.go index ff012a0f6e045afbc40708b96e5a04d3373c336d..5b83868c2accdcca59146ad5178f3413167c4d33 100644 --- a/cmd/gosdn-tview/views/resultAndInputView.go +++ b/cmd/gosdn-tview/views/resultAndInputView.go @@ -24,7 +24,7 @@ func NewResultAndInputView(title string, app *app.App, commandListView tview.Pri pages: tview.NewPages(), pndInputView: NewAddPNDView("add PND", app), resultView: tview.NewTextView(), - consoleLogView: NewConsoleLogView("logs", conn), + consoleLogView: NewConsoleLogView("logs", app, conn), } rv.resultView. SetDynamicColors(true). diff --git a/configs/gosdn.toml b/configs/gosdn.toml index a66664646568ef38ca747be1f6fa359fb88eb3f9..b0ba9553705d34d970728f39669bb770e252487b 100644 --- a/configs/gosdn.toml +++ b/configs/gosdn.toml @@ -1,4 +1,3 @@ -# example config CliSocket = "localhost:55055" DatabaseSocket = "bolt://172.17.0.4:7687" DatabaseUser = "" diff --git a/nucleus/cli-handling.go b/nucleus/cli-handling.go index f4e41b78f31ebf950e1b1b8a84c2938038dbcbe7..b95e9094300432d7db949dcaac9e84da5a01100e 100644 --- a/nucleus/cli-handling.go +++ b/nucleus/cli-handling.go @@ -17,6 +17,8 @@ import ( "code.fbi.h-da.de/cocsn/gosdn/log" "code.fbi.h-da.de/cocsn/gosdn/sbi/restconf/client/ciena" "google.golang.org/grpc" + "google.golang.org/grpc/health" + healthpb "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/protobuf/types/known/emptypb" ) @@ -99,8 +101,11 @@ func (s *server) Shutdown(ctx context.Context, in *pb.ShutdownRequest) (*pb.Shut func getCLIGoing(core *Core) { - var logConnections []*logConnection - var logBuffer buf + var ( + logConnections []*logConnection + logBuffer buf + system = "" + ) log.Info("Starting: GetCLIGoing") // Boot-up the control interface for the cli @@ -110,14 +115,18 @@ func getCLIGoing(core *Core) { } cliControlServer := grpc.NewServer() + healthCheck := health.NewServer() srv = &server{core: core, logConnections: logConnections} //TODO: move? wrt := io.MultiWriter(os.Stdout, &logBuffer) log.Output(wrt) + healthpb.RegisterHealthServer(cliControlServer, healthCheck) pb.RegisterGrpcCliServer(cliControlServer, srv) + healthCheck.SetServingStatus(system, healthpb.HealthCheckResponse_SERVING) + if err := cliControlServer.Serve(cliControlListener); err != nil { log.Fatal(err) }