Skip to content
Snippets Groups Projects
Commit 1ae7c33e authored by Manuel Kieweg's avatar Manuel Kieweg
Browse files

Merge branch 'develop' into '74-follow-up-from-draft-resolve-pnd-handling-via-cli-and-database'

# Conflicts:
#   cmd/gnmi/gnmi.go
#   nucleus/device.go
#   nucleus/principalNetworkDomain.go
#   nucleus/transport.go
parents f315d9dd b36faa46
Branches
Tags
2 merge requests!116Resolve "Transport Tests",!90Develop
Pipeline #65970 passed with warnings
Showing
with 79 additions and 1146 deletions
......@@ -13,4 +13,5 @@ include:
- local: '/build/ci/.code-quality-ci.yml'
- local: '/build/ci/.documentation-ci.yml'
- local: '/build/ci/.security-and-compliance-ci.yml'
- local: '/build/ci/.build-container.yml'
\ No newline at end of file
- local: '/build/ci/.build-container.yml'
- local: '/build/ci/.test.yml'
\ No newline at end of file
# GoSDN
### CI Status Master
[![coverage report](https://code.fbi.h-da.de/cocsn/gosdn/badges/master/coverage.svg)](https://code.fbi.h-da.de/cocsn/gosdn/-/commits/master)
[![pipeline status](https://code.fbi.h-da.de/cocsn/gosdn/badges/master/pipeline.svg)](https://code.fbi.h-da.de/cocsn/gosdn/-/commits/master)
### CI Status Develop
[![coverage report](https://code.fbi.h-da.de/cocsn/gosdn/badges/develop/coverage.svg)](https://code.fbi.h-da.de/cocsn/gosdn/-/commits/develop)
[![pipeline status](https://code.fbi.h-da.de/cocsn/gosdn/badges/develop/pipeline.svg)](https://code.fbi.h-da.de/cocsn/gosdn/-/commits/develop)
The GIT repo for the GoSDN design and implementation. GoSDN is intended to be controller for Software Defined Networks (SDN) that follows a modern software architecture design and a well-documented implementation in the go language.
## Generate Code Stubs
......@@ -15,4 +25,4 @@ gosdn
## Documentation
The latest documentatiion generated on the master branch can be downloaded [here](https://code.fbi.h-da.de/cocsn/gosdn/-/jobs).
\ No newline at end of file
The latest documentatiion generated on the master branch can be downloaded [here](https://code.fbi.h-da.de/cocsn/gosdn/-/jobs).
unit-test-master:
image: golang:1.14
stage: test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- go test -race $(go list ./... | grep -v /forks/ | grep -v /api/ | grep -v /mocks ) -v -coverprofile=coverage.out
after_script:
- go tool cover -func=coverage.out
unit-test:
image: golang:1.14
stage: test
allow_failure: true
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME != $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
script:
- go test -race $(go list ./... | grep -v /forks/ | grep -v /api/ | grep -v /mocks ) -v -coverprofile=coverage.out
after_script:
- go tool cover -func=coverage.out
\ No newline at end of file
......@@ -6,7 +6,6 @@ import (
oc "code.fbi.h-da.de/cocsn/yang-models/generated/arista"
"context"
"flag"
"fmt"
"github.com/google/gnxi/utils/credentials"
pb "github.com/openconfig/gnmi/proto/gnmi"
"github.com/openconfig/ygot/ygot"
......@@ -16,7 +15,6 @@ import (
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
"net"
"os"
"reflect"
)
......@@ -76,19 +74,6 @@ func main() {
oc.Unmarshal,
oc.ΛEnum)
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Supported models:\n")
for _, m := range model.SupportedModels() {
fmt.Fprintf(os.Stderr, " %s\n", m)
}
fmt.Fprintf(os.Stderr, "\n")
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
flag.PrintDefaults()
}
flag.Set("logtostderr", "true")
flag.Parse()
g := grpc.NewServer()
var configData []byte
......
......@@ -30,7 +30,21 @@ func main() {
log.Fatal(err)
}
p := []string{"/interfaces/interface[name=*]/state/name"}
cfg := &gnmi.Config{
Addr: "[fdfd::ce05]:6030",
Username: "admin",
Password: "arista",
Encoding: gpb.Encoding_JSON_IETF,
}
transport, err := nucleus.NewGnmiTransport(cfg)
if err != nil {
log.Fatal(err)
}
transport.SetNode = sbi.SetNode()
device.Transport = transport
p := []string{"/interfaces/interface"}
errors := 0
for _, path := range p {
err := pnd.RequestAll(path)
......
package main
import (
pb "code.fbi.h-da.de/cocsn/gosdn/api/proto"
"context"
"flag"
"fmt"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"os"
"time"
)
const (
defaultName = "gosdn-cli"
)
// Based on the helloworld example of grpc.io -- thx!
type cliClientConfig struct {
goSDNCLIAddr4 *string
goSDNCLIPort4 *int
interactive *bool
goSDNCommand *string
}
type commandOptions struct {
name string
description string
command func(conn *grpc.ClientConn)
}
var commandList = map[string]commandOptions{
"hello": {"hello", "test connection to goSDN controller", goSDNSayHello},
"shutdown": {"shutdown", "request goSDN controller to shutdown", goSDNShutdown},
"testdb": {"testdb", "test all database connections", goSDNTestDB},
"tapigetedge": {"tapigetedge", "get list of edges", TAPIGetEdge},
"tapigetedgenode": {"tapigetedgenode", "get list of edgenodes", TAPIGetEdgeNode},
"tapigetlink": {"tapigetlink", "get list of links", TAPIGetLink},
}
/*
gosdn-cli allows to mode of operations:
- interactive: text GUI to operate goSDN
- non-interactive: basic CLI without text GUI
*/
func main() {
// This holds the basic configuration for gosdn-cli
var myConfiguration = new(cliClientConfig)
myConfiguration.goSDNCLIAddr4 = flag.String("cliServerAddr", "127.0.0.1", "The IPv4 Address of the grpcCLI.")
myConfiguration.goSDNCLIPort4 = flag.Int("cliServerPort", 55055, "The port number of the grpcCLI")
myConfiguration.interactive = flag.Bool("interactive", false, "interactive: text gui or just not")
var printCommandList = flag.Bool("commandlist", false, "interactive: print command list")
myConfiguration.goSDNCommand = flag.String("command", "", "-command: <your command> ; show commands with -commandlist")
flag.Parse()
// Print complete command list and exit
if *printCommandList {
for _, element := range commandList {
fmt.Println(element.name + "\t" + element.description)
}
os.Exit(0)
}
log.Info("Starting " + defaultName + " to access the goSDN controller")
// Prepare string with socket for connection to the goSDN controller
goSDNSocketAddress := fmt.Sprintf("%s:%d", *myConfiguration.goSDNCLIAddr4, *myConfiguration.goSDNCLIPort4)
log.Info("Connecting to the goSDN server at: " + goSDNSocketAddress)
// Set up a connection to the server.
address := "localhost:55055"
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatal(err)
}
defer conn.Close()
log.Info("Connected to " + conn.Target())
// Check for non-interactive or interactive mode
if !*myConfiguration.interactive {
log.Info("starting in non-interactive mode")
// Lookup command or die
_, found := commandList[*myConfiguration.goSDNCommand]
if found {
// Excecute desired command
commandList[*myConfiguration.goSDNCommand].command(conn)
} else {
// TODO: change once gosdn/errors exist
log.Fatal("Your desired command is not available: ", commandList[*myConfiguration.goSDNCommand].name)
os.Exit(1)
}
} else {
log.Info("starting in interactive mode -- do not use yet")
os.Exit(1)
}
}
func goSDNSayHello(conn *grpc.ClientConn) {
c := pb.NewGrpcCliClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatal(err)
}
log.Info("Greeting: ", r.String())
}
func goSDNShutdown(conn *grpc.ClientConn) {
c := pb.NewGrpcCliClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[0]
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.Shutdown(ctx, &pb.ShutdownRequest{Name: name})
if err != nil {
log.Fatal(err)
}
log.Info("Greeting: ", r.GetMessage())
}
func goSDNTestDB(conn *grpc.ClientConn) {
// TODO: fill with code and also see if grpc interface has this stub implemented.
}
// TAPIGetEdge triggers the GetEdge function of the Ciena
// flavoured TAPI ciena
func TAPIGetEdge(conn *grpc.ClientConn) {
c := pb.NewGrpcCliClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[0]
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.TAPIGetEdge(ctx, &pb.TAPIRequest{Name: name})
if err != nil {
log.Fatal(err)
}
log.Info("TAPIGetEdge said: ", r.GetMessage())
}
// TAPIGetEdgeNode triggers the GetEdgeNode function of the Ciena
// flavoured TAPI ciena
func TAPIGetEdgeNode(conn *grpc.ClientConn) {
c := pb.NewGrpcCliClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[0]
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.TAPIGetEdgeNode(ctx, &pb.TAPIRequest{Name: name})
if err != nil {
log.Fatal(err)
}
log.Info("TAPIGetEdgeNode said: ", r.GetMessage())
}
// TAPIGetLink triggers the GetLink function of the Ciena
// flavoured TAPI ciena
func TAPIGetLink(conn *grpc.ClientConn) {
c := pb.NewGrpcCliClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[0]
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.TAPIGetLink(ctx, &pb.TAPIRequest{Name: name})
if err != nil {
log.Fatal(err)
}
log.Info("TAPIGetLink said: ", r.GetMessage())
}
package app
//TODO: App should be a Singleton i guess
import (
"github.com/rivo/tview"
)
type view interface {
GetContent() tview.Primitive
}
//App is a GUI-Application bases on tview
type App struct {
app *tview.Application
pages *tview.Pages
}
//NewApp creates a new GUI-Application
func NewApp() *App {
a := &App{
app: tview.NewApplication(),
}
return a
}
//SetRoot sets the root of the GUI-Application
func (a *App) SetRoot(v view) {
a.pages = v.GetContent().(*tview.Pages)
a.app.SetRoot(a.pages, true)
}
//SwitchPage switches to the given (as string) tview page
func (a *App) SwitchPage(s string) {
if a.pages.HasPage(s) {
a.pages.SwitchToPage(s)
}
}
//AddPage adds a new view as page that can be switched to
func (a *App) AddPage(name string, p view) {
a.pages.AddPage(name, p.GetContent(), true, false)
}
//Run starts the GUI-Application
func (a *App) Run() error {
return a.app.Run()
}
//Stop stops the GUI-Application
func (a *App) Stop() {
a.app.Stop()
}
//Draw calls tview.Draw()
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)
}
package commands
import (
"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"
)
const (
defaultName = "gosdn-cli"
)
type command struct {
Name string
Description string
Function func(conn *grpc.ClientConn) string
}
//CommandList contains the specific goSDN gRPC calls
var CommandList = []command{
{"hello", "test connection to controller", goSDNSayHello},
{"testdb", "test all database connections", goSDNTestDB},
{"tapiGetDevices", "creates devices", TAPIGetEdge},
{"tapiGetInterfaces", "creates interfaces", TAPIGetEdgeNode},
{"tapiGetLinks", "creates links between devices", TAPIGetLink},
{"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) {
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(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 {
return err
}
go func(stream pb.GrpcCli_CreateLogStreamClient) {
for {
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)
app.QueueUpdateDraw(func() {
tv.Write(response)
})
}
}(stream)
return streamError
}
func goSDNSayHello(conn *grpc.ClientConn) string {
c := pb.NewGrpcCliClient(conn)
// Contact the server and print out its response.
name := defaultName
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
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)
// Contact the server and print out its response.
name := defaultName
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.Shutdown(ctx, &pb.ShutdownRequest{Name: name})
if err != nil {
return err.Error()
}
return r.GetMessage()
}
func goSDNTestDB(conn *grpc.ClientConn) string {
// TODO: fill with code and also see if grpc interface has this stub implemented.
return "not implemented yet"
}
// TAPIGetEdge triggers the GetEdge function of the Ciena
// flavoured TAPI ciena
func TAPIGetEdge(conn *grpc.ClientConn) string {
c := pb.NewGrpcCliClient(conn)
// Contact the server and print out its response.
name := defaultName
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.TAPIGetEdge(ctx, &pb.TAPIRequest{Name: name})
if err != nil {
return err.Error()
}
return r.GetMessage()
}
// TAPIGetEdgeNode triggers the GetEdgeNode function of the Ciena
// flavoured TAPI ciena
func TAPIGetEdgeNode(conn *grpc.ClientConn) string {
c := pb.NewGrpcCliClient(conn)
// Contact the server and print out its response.
name := defaultName
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.TAPIGetEdgeNode(ctx, &pb.TAPIRequest{Name: name})
if err != nil {
return err.Error()
}
return r.GetMessage()
}
// TAPIGetLink triggers the GetLink function of the Ciena
// flavoured TAPI ciena
func TAPIGetLink(conn *grpc.ClientConn) string {
c := pb.NewGrpcCliClient(conn)
// Contact the server and print out its response.
name := defaultName
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.TAPIGetLink(ctx, &pb.TAPIRequest{Name: name})
if err != nil {
return err.Error()
}
return r.GetMessage()
}
package main
import (
"flag"
"strconv"
"strings"
"code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/app"
grpc "code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/grpc"
"code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/views"
log "github.com/sirupsen/logrus"
)
func main() {
addrIPv4 := flag.String("addr", "localhost", "IPv4 server adress to connect to")
port := flag.Int("port", 55055, "Port of gRPC")
flag.Parse()
addr := strings.Join([]string{*addrIPv4, strconv.Itoa(*port)}, ":")
conn, err := grpc.Connect(addr)
if err != nil {
log.Fatal(err)
}
app := app.NewApp()
mainView := views.NewMainView(app, conn)
app.SetRoot(mainView)
app.Run()
defer app.Stop()
}
package views
import (
"code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/app"
"github.com/rivo/tview"
)
//AddPNDView is an application view to create a new goSDN PND
type AddPNDView struct {
title string
addPNDView *tview.Form
}
//NewAddPNDView creates a new AddPNDView
func NewAddPNDView(title string, app *app.App) *AddPNDView {
pndv := &AddPNDView{
title: title,
addPNDView: tview.NewForm(),
}
pndv.addPNDView.
SetBorder(true).
SetTitle(pndv.title)
pndv.addPNDView.
AddInputField("Name", "", 20, nil, nil).
AddInputField("description", "", 20, nil, nil).
AddDropDown("SI", []string{"Southbound 1", "Southbound 2", "Southbound 3", "Southbound 4"}, 0, nil).
AddButton("Send", func() {
//TODO: call grpc function here
}).
AddButton("Abort", func() {
app.SwitchPage("main")
}).
SetCancelFunc(func() {
app.SwitchPage("main")
})
return pndv
}
//GetContent returns the tview.Primitive belonging to the AddPNDView
func (pndv *AddPNDView) GetContent() tview.Primitive {
return pndv.addPNDView
}
//GetTitle returns the title of the specific view
func (pndv *AddPNDView) GetTitle() string {
return pndv.title
}
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"
)
//CommandListView is an application view to display all the goSDN commands
type CommandListView struct {
title string
commandsList *tview.List
}
//NewCommandListView creates a new CommandListView
func NewCommandListView(title string) *CommandListView {
cv := &CommandListView{
title: title,
commandsList: tview.NewList(),
}
cv.commandsList.
SetBorder(true).
SetTitle(cv.title)
return cv
}
//GetContent returns the tview.Primitive belonging to the CommandListView
func (cv *CommandListView) GetContent() tview.Primitive {
return cv.commandsList
}
//GetTitle returns the title of the specific view
func (cv *CommandListView) GetTitle() string {
return cv.title
}
//GetCommands gets all goSDN commands from a command list and creates new
//tview.List items for each one of them. The specific gRPC functions are added
//as tview.Selected() function
func (cv *CommandListView) GetCommands(app *app.App, rv *ResultAndInputView,
conn *grpc.ClientConn) {
//TODO: create own command in grpc -> commands
cv.commandsList.AddItem("addPND", "closes the application", 'a', func() {
rv.ChangeContentView(rv.pndInputView.GetTitle())
app.SetFocus(rv.GetContent())
})
for i, command := range commands.CommandList {
f := command.Function
cv.commandsList.
AddItem(command.Name, command.Description, rune('b'+i), func() {
r := f(conn)
rv.SetContent(r)
rv.ChangeContentView(rv.GetTitle())
app.SetFocus(rv.GetContent())
})
}
cv.commandsList.AddItem("log", "shows the log of goSDN", 'l', func() {
rv.ChangeContentView(rv.consoleLogView.GetTitle())
app.SetFocus(rv.GetContent())
})
cv.commandsList.AddItem("quit", "closes the application", 'q', func() {
app.Stop()
})
}
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"
)
//ConsoleLogView is an application view to create a view for goSDN log messages
type ConsoleLogView struct {
title string
consoleLogView *tview.TextView
}
//NewConsoleLogView creates a new ConsoleLogView
func NewConsoleLogView(title string, app *app.App, conn *grpc.ClientConn) *ConsoleLogView {
clv := &ConsoleLogView{
consoleLogView: tview.NewTextView(),
title: title,
}
clv.consoleLogView.
SetDynamicColors(true).
SetTextAlign(tview.AlignLeft).
SetScrollable(true).
SetRegions(true).
SetBorder(true).
SetTitle(clv.title)
commands.GoSDNLogStream(app, conn, clv.consoleLogView)
return clv
}
//GetContent returns the tview.Primitive belonging to the ConsoleLogView
func (clv *ConsoleLogView) GetContent() tview.Primitive {
return clv.consoleLogView
}
//Write implements the io.Writer interface via tview.textView.Write().
//Gets a string and converts it to a byte slice and writes it to the textView
//buffer
func (clv *ConsoleLogView) Write(s string) (n int, err error) {
b := []byte(s)
return clv.consoleLogView.Write(b)
}
//GetTitle returns the title of the specific view
func (clv *ConsoleLogView) GetTitle() string {
return clv.title
}
package views
import (
"code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/app"
"github.com/rivo/tview"
//"time"
)
//DatabaseStatusView is an application view to display the current status of
//the database (e.g. connected, disconnected,...)
type DatabaseStatusView struct {
databaseStatusView *tview.TextView
}
//NewDatabaseStatusView creates a new DatabaseStatusView
func NewDatabaseStatusView(app *app.App) *DatabaseStatusView {
dv := &DatabaseStatusView{
databaseStatusView: tview.NewTextView(),
}
dv.databaseStatusView.
SetDynamicColors(true).
SetTextAlign(tview.AlignCenter).
SetRegions(true).
SetBorder(true).
SetTitle("Database")
//TODO: dummy text at the moment. database status check has to be initilized
//here
dv.databaseStatusView.SetText("[green]" + "connected")
return dv
}
//GetContent returns the tview.Primitive belonging to the DatabaseStatusView
func (dv *DatabaseStatusView) GetContent() tview.Primitive {
return dv.databaseStatusView
}
//SetContent sets new string content for the DatabaseStatusView
func (dv *DatabaseStatusView) SetContent(s string) {
dv.databaseStatusView.Clear()
dv.databaseStatusView.SetText(s)
}
package views
import (
"code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/app"
"github.com/rivo/tview"
"google.golang.org/grpc"
)
//FooterView is an application view to display the footer of the application it
//consists multiple other application views
type FooterView struct {
footerView *tview.Flex
databaseStatusView *DatabaseStatusView
gRPCStatusView *GRPCStatusView
}
//NewFooterView creates a new FooterView
func NewFooterView(app *app.App, conn *grpc.ClientConn) *FooterView {
fw := &FooterView{
footerView: tview.NewFlex(),
databaseStatusView: NewDatabaseStatusView(app),
gRPCStatusView: NewGRPCStatusView(app, conn),
}
fw.footerView.
AddItem(fw.gRPCStatusView.GetContent(), 0, 1, false).
AddItem(fw.databaseStatusView.GetContent(), 0, 1, false)
return fw
}
//GetContent returns the tview.Primitive belonging to the FooterView
func (fw *FooterView) GetContent() tview.Primitive {
return fw.footerView
}
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"
)
//GRPCStatusView is an application view to display the current status of
//the gRPC server (e.g. connected, unavailable,...)
type GRPCStatusView struct {
gRPCStatusView *tview.TextView
}
//NewGRPCStatusView creates a new GRPCStatusView
func NewGRPCStatusView(app *app.App, conn *grpc.ClientConn) *GRPCStatusView {
sv := &GRPCStatusView{
gRPCStatusView: tview.NewTextView(),
}
sv.gRPCStatusView.
SetDynamicColors(true).
SetTextAlign(tview.AlignCenter).
SetRegions(true).
SetBorder(true).
SetTitle("gRPC")
commands.WatchHealth("", app, conn, sv.gRPCStatusView)
return sv
}
//GetContent returns the tview.Primitive belonging to the gRPCStatusView
func (sv *GRPCStatusView) GetContent() tview.Primitive {
return sv.gRPCStatusView
}
//SetContent sets new string content for the gRPCStatusView
func (sv *GRPCStatusView) SetContent(s string) {
sv.gRPCStatusView.Clear()
sv.gRPCStatusView.SetText(s)
}
package views
import "github.com/rivo/tview"
var goSDNAscii = ` ____ ____ _ _ _ __ _ _ _
__ _ ___/ ___|| _ \| \ | | __| | __ _ / / __ ___| |_ | |__ __| | __ _
/ _ |/ _ \___ \| | | | \| | _____ / _ |/ _ | / / _ \ / _ \ __| _____ | _ \ / _ |/ _ |
| (_| | (_) |__) | |_| | |\ | |_____| | (_| | (_| |/ /| | | | __/ |_ |_____| | | | | | (_| | (_| |
\__, |\___/____/|____/|_| \_| \__,_|\__,_/_/ |_| |_|\___|\__| |_| |_|___\__,_|\__,_|
|___/ |_____| `
//HeaderView is an application view to display the header of the application
type HeaderView struct {
headerFlex *tview.Flex
titleView *tview.TextView
}
//NewHeaderView creates a new HeaderView
func NewHeaderView() *HeaderView {
//TODO: change to uses FlexBox if there is more to display in the header
hv := &HeaderView{
titleView: tview.NewTextView(),
}
hv.titleView.
SetText(goSDNAscii).
SetTextAlign(tview.AlignCenter).
SetBorder(true)
return hv
}
//GetContent returns the tview.Primitive belonging to the HeaderView
func (hv *HeaderView) GetContent() tview.Primitive {
return hv.titleView
}
package views
import (
"code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/app"
"github.com/rivo/tview"
"google.golang.org/grpc"
)
//MainView is an application view to display the main content of the application.
//It is the entry point for the application and contains all necessary views for
//for that purpose
type MainView struct {
pages *tview.Pages
mainGrid *tview.Grid
commandsListView *CommandListView
resultAndInputView *ResultAndInputView
headerView *HeaderView
footerView *FooterView
}
//NewMainView creates a new MainView
func NewMainView(app *app.App, conn *grpc.ClientConn) *MainView {
mv := &MainView{
pages: tview.NewPages(),
mainGrid: tview.NewGrid(),
commandsListView: NewCommandListView("commands"),
headerView: NewHeaderView(),
footerView: NewFooterView(app, conn),
}
mv.resultAndInputView = NewResultAndInputView("result and input", app, mv.commandsListView.GetContent(), conn)
mv.commandsListView.GetCommands(app, mv.resultAndInputView, conn)
mv.mainGrid.
SetRows(8, 0, 5).
SetColumns(40, 0).
AddItem(mv.headerView.GetContent(), 0, 0, 1, 2, 0, 0, false).
AddItem(mv.footerView.GetContent(), 2, 0, 1, 2, 0, 0, false)
mv.mainGrid.
AddItem(mv.commandsListView.GetContent(), 1, 0, 1, 1, 0, 0, true).
AddItem(mv.resultAndInputView.GetContent(), 1, 1, 1, 1, 0, 0, false)
mv.pages.AddPage("main", mv.mainGrid, true, true)
return mv
}
//GetContent returns the tview.Primitive belonging to the MainView
func (mv *MainView) GetContent() tview.Primitive {
return mv.pages
}
package views
import (
"code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/app"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"google.golang.org/grpc"
)
//ResultAndInputView is an application view to display different other views.
//Depending on the required features the views are changed.
type ResultAndInputView struct {
title string
pages *tview.Pages
resultView *tview.TextView
pndInputView *AddPNDView
consoleLogView *ConsoleLogView
}
//NewResultAndInputView creates a new ResultAndInputView
func NewResultAndInputView(title string, app *app.App, commandListView tview.Primitive, conn *grpc.ClientConn) *ResultAndInputView {
rv := &ResultAndInputView{
title: title,
pages: tview.NewPages(),
pndInputView: NewAddPNDView("add PND", app),
resultView: tview.NewTextView(),
consoleLogView: NewConsoleLogView("logs", app, conn),
}
rv.resultView.
SetDynamicColors(true).
SetRegions(true).
SetScrollable(true).
SetTitle(rv.title).
SetBorder(true)
rv.pages.
AddPage(rv.title, rv.resultView, true, true).
AddPage(rv.pndInputView.GetTitle(), rv.pndInputView.GetContent(), true, false).
AddPage(rv.consoleLogView.GetTitle(), rv.consoleLogView.GetContent(), true, false)
rv.pages.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
k := event.Key()
if k == tcell.KeyESC {
app.SetFocus(commandListView)
}
return event
})
return rv
}
//GetContent returns the tview.Primitive belonging to the ResultAndInputView
func (rv *ResultAndInputView) GetContent() tview.Primitive {
return rv.pages
}
//ChangeContentView changes the current content (tview.Primitive) that is visible
//inside this view
func (rv *ResultAndInputView) ChangeContentView(s string) {
rv.pages.SwitchToPage(s)
}
//SetContent sets new string content for the ResultAndInputView
func (rv *ResultAndInputView) SetContent(s string) {
rv.resultView.Clear()
rv.resultView.SetText(s)
}
//GetTitle returns the title of the specific view
func (rv *ResultAndInputView) GetTitle() string {
return rv.title
}
......@@ -7,19 +7,20 @@ import (
"github.com/spf13/viper"
)
//Database is a database
// Database is a database
// deprecated
type Database struct {
driver neo4j.Driver
}
//PND is a principle network domain
// PND is a principle network domain
type PND struct {
name string
description string
interfaces []string
}
//NewDatabaseClient creates a database ciena
// NewDatabaseClient creates a database ciena
func NewDatabaseClient() Database {
uri := viper.GetString("db.socket")
username := viper.GetString("db.user")
......@@ -32,7 +33,7 @@ func NewDatabaseClient() Database {
}
}
//createDriver creates a neo4j.Driver instance
// createDriver creates a neo4j.Driver instance
func createDriver(uri, username, password string, encrypted bool) neo4j.Driver {
driver, err := neo4j.NewDriver(
uri,
......@@ -49,7 +50,7 @@ func createDriver(uri, username, password string, encrypted bool) neo4j.Driver {
return driver
}
//createSession creates a neo4j.Session
// createSession creates a neo4j.Session
func createSession(driver neo4j.Driver, write bool) neo4j.Session {
var sessionConfig neo4j.SessionConfig
......@@ -68,7 +69,7 @@ func createSession(driver neo4j.Driver, write bool) neo4j.Session {
return session
}
//storePndTxFunc transaction to store a pnd in the database
// storePndTxFunc transaction to store a pnd in the database
func storePndTxFunc(name, description string, interfaces []string) neo4j.TransactionWork {
return func(tx neo4j.Transaction) (interface{}, error) {
query :=
......@@ -98,7 +99,7 @@ func storePndTxFunc(name, description string, interfaces []string) neo4j.Transac
}
}
//StorePND stores the given principle network domain
// StorePND stores the given principle network domain
func (d Database) StorePND(pnd *PND) neo4j.Node {
session := createSession(d.driver, true)
defer session.Close()
......@@ -170,8 +171,8 @@ func storeNodesTxFunc(json string, id int64) neo4j.TransactionWork {
}
}
//StoreNodes stores the given nodes to the database and adds them to a
//principle networt domain (PND). It is required for a node to belong to a PND.
// StoreNodes stores the given nodes to the database and adds them to a
// principle networt domain (PND). It is required for a node to belong to a PND.
func (d Database) StoreNodes(json string) []neo4j.Node {
//TODO: remove this after testing and add own gRPC call for it
testPND := PND{name: "test_PND", description: "very interesting", interfaces: []string{"TAPI", "RESTCONF"}}
......@@ -189,15 +190,15 @@ func (d Database) StoreNodes(json string) []neo4j.Node {
return result.([]neo4j.Node)
}
//RemoveNodes removes the given nodes and their relationships
// RemoveNodes removes the given nodes and their relationships
func (d Database) RemoveNodes(json string) {}
//RemoveSingleNode removes the given node and their relationship by id.
// RemoveSingleNode removes the given node and their relationship by id.
func (d Database) RemoveSingleNode(id string) {}
//storeLinksTxFunc transaction to store links from a json.
//creates relation between different devices.
//returns a slice of those created relations.
// storeLinksTxFunc transaction to store links from a json.
// creates relation between different devices.
// returns a slice of those created relations.
func storeLinksTxFunc(json string) neo4j.TransactionWork {
return func(tx neo4j.Transaction) (interface{}, error) {
var relationsList []neo4j.Relationship
......@@ -235,7 +236,7 @@ func storeLinksTxFunc(json string) neo4j.TransactionWork {
}
}
//StoreLinks stores the links between nodes
// StoreLinks stores the links between nodes
func (d Database) StoreLinks(json string) []neo4j.Relationship {
session := createSession(d.driver, true)
defer session.Close()
......@@ -250,8 +251,8 @@ func (d Database) StoreLinks(json string) []neo4j.Relationship {
return result.([]neo4j.Relationship)
}
//storeNodeEdgePointsTxFunc transaction to store interfaces from a json.
//returns count of added/updated interfaces
// storeNodeEdgePointsTxFunc transaction to store interfaces from a json.
// returns count of added/updated interfaces
func storeNodeEdgePointsTxFunc(json string) neo4j.TransactionWork {
return func(tx neo4j.Transaction) (interface{}, error) {
query :=
......@@ -287,8 +288,8 @@ func storeNodeEdgePointsTxFunc(json string) neo4j.TransactionWork {
//TODO: currently this goes over each and every device/interface and adds
// a interface_of relation. -> do it only for the newly added interfaces
//storeNodeEdgePointsRelationTxFunc transaction to create relations between interfaces and devices
//returns count of added/updated relations
// storeNodeEdgePointsRelationTxFunc transaction to create relations between interfaces and devices
// returns count of added/updated relations
func storeNodeEdgePointsRelationTxFunc() neo4j.TransactionWork {
return func(tx neo4j.Transaction) (interface{}, error) {
query :=
......@@ -314,7 +315,7 @@ func storeNodeEdgePointsRelationTxFunc() neo4j.TransactionWork {
}
}
//StoreNodeEdgePoints stores the given node edge points (interfaces)
// StoreNodeEdgePoints stores the given node edge points (interfaces)
func (d Database) StoreNodeEdgePoints(json string) {
session := createSession(d.driver, true)
defer session.Close()
......@@ -332,27 +333,27 @@ func (d Database) StoreNodeEdgePoints(json string) {
log.Info("added/updated nodeEdgePoints (count): ", result)
}
//StoreConnections stores relations between nodes
// StoreConnections stores relations between nodes
func (d Database) StoreConnections(json string) {}
//StoreTopology creates a new network topology node. Can also create a relation
// StoreTopology creates a new network topology node. Can also create a relation
//the new node and a existing one if desired
func StoreTopology() {}
//RemoveTopology removes the given network topology. This includes the node itself
// RemoveTopology removes the given network topology. This includes the node itself
//aswell as the containing links and relations
func RemoveTopology() {}
//CreateTopologyRelation creates a relation between two given topologies
// CreateTopologyRelation creates a relation between two given topologies
func CreateTopologyRelation() {}
//CreateLink creates a link between two network elements
// CreateLink creates a link between two network elements
func CreateLink() {}
//RemoveLink removes a link between two network elements
// RemoveLink removes a link between two network elements
func RemoveLink() {}
//Shutdown closes the connection to the database
// Shutdown closes the connection to the database
func (d Database) Shutdown() error {
return d.driver.Close()
}
// Copyright (c) 2016 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.
// Package ciena provides helper functions for OpenConfig CLI tools.
package client
import (
"io"
"strings"
"sync"
"github.com/golang/glog"
"github.com/golang/protobuf/proto"
"github.com/openconfig/reference/rpc/openconfig"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
const defaultPort = "6030"
// PublishFunc is the method to publish responses
type PublishFunc func(addr string, message proto.Message)
// Client is a connected gRPC ciena
type Client struct {
client openconfig.OpenConfigClient
ctx context.Context
device string
}
// New creates a new gRPC ciena and connects it
func New(username, password, addr string, opts []grpc.DialOption) *Client {
device := addr
if !strings.ContainsRune(addr, ':') {
addr += ":" + defaultPort
}
// Make sure we don't move past the grpc.Dial() call until we actually
// established an HTTP/2 connection successfully.
opts = append(opts, grpc.WithBlock())
conn, err := grpc.Dial(addr, opts...)
if err != nil {
glog.Fatalf("Failed to dial: %s", err)
}
glog.Infof("Connected to %s", addr)
client := openconfig.NewOpenConfigClient(conn)
ctx := context.Background()
if username != "" {
ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(
"username", username,
"password", password))
}
return &Client{
client: client,
device: device,
ctx: ctx,
}
}
// Get sends a get request and returns the responses
func (c *Client) Get(path string) []*openconfig.Notification {
req := &openconfig.GetRequest{
Path: []*openconfig.Path{
{
Element: strings.Split(path, "/"),
},
},
}
response, err := c.client.Get(c.ctx, req)
if err != nil {
glog.Fatalf("Get failed: %s", err)
}
return response.Notification
}
// Subscribe sends subscriptions, and consumes responses.
// The given publish function is used to publish SubscribeResponses received
// for the given subscriptions, when connected to the given host, with the
// given user/pass pair, or the ciena-side cert specified in the gRPC opts.
// This function does not normally return so it should probably be run in its
// own goroutine. When this function returns, the given WaitGroup is marked
// as done.
func (c *Client) Subscribe(wg *sync.WaitGroup, subscriptions []string,
publish PublishFunc) {
defer wg.Done()
stream, err := c.client.Subscribe(c.ctx)
if err != nil {
glog.Fatalf("Subscribe failed: %s", err)
}
defer stream.CloseSend()
for _, path := range subscriptions {
sub := &openconfig.SubscribeRequest{
Request: &openconfig.SubscribeRequest_Subscribe{
Subscribe: &openconfig.SubscriptionList{
Subscription: []*openconfig.Subscription{
{
Path: &openconfig.Path{Element: strings.Split(path, "/")},
},
},
},
},
}
glog.Infof("Sending subscribe request: %s", sub)
err = stream.Send(sub)
if err != nil {
glog.Fatalf("Failed to subscribe: %s", err)
}
}
for {
resp, err := stream.Recv()
if err != nil {
if err != io.EOF {
glog.Fatalf("Error received from the server: %s", err)
}
return
}
switch resp := resp.Response.(type) {
case *openconfig.SubscribeResponse_SyncResponse:
if !resp.SyncResponse {
panic("initial sync failed," +
" check that you're using a ciena compatible with the server")
}
}
glog.V(3).Info(resp)
publish(c.device, resp)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment