From 4ffc66f4342ec18c4ce8dc8723d7870a377eb273 Mon Sep 17 00:00:00 2001
From: Manuel Kieweg <manuel.kieweg@h-da.de>
Date: Wed, 9 Jun 2021 18:18:57 +0200
Subject: [PATCH] create csbi device implemented

---
 cli/grpc.go                            |  4 ++
 controller.go                          | 22 +++++++-
 go.mod                                 |  2 +-
 go.sum                                 |  4 +-
 northbound/server/core.go              |  2 +-
 northbound/server/csbi.go              | 27 +++++++++
 northbound/server/nbi.go               |  2 +
 nucleus/device.go                      | 20 ++++---
 nucleus/principalNetworkDomain.go      | 77 +++++++++++++++++++++++++-
 nucleus/principalNetworkDomain_test.go |  2 +-
 nucleus/southbound.go                  | 30 ++++++++++
 nucleus/store.go                       | 21 ++++++-
 12 files changed, 194 insertions(+), 19 deletions(-)
 create mode 100644 northbound/server/csbi.go

diff --git a/cli/grpc.go b/cli/grpc.go
index 3d731ab90..1bd0218e0 100644
--- a/cli/grpc.go
+++ b/cli/grpc.go
@@ -195,6 +195,10 @@ func AddDevice(addr, username, password, sbi, pnd, deviceAddress, deviceName str
 		},
 		Pid: pnd,
 	}
+	if sbi == "csbi" {
+		req.Ond[0].Sbi = nil
+		req.Ond[0].TransportOption.Csbi = true
+	}
 	ctx := context.Background()
 	return pndClient.Set(ctx, req)
 }
diff --git a/controller.go b/controller.go
index da943ff12..bd8805e33 100644
--- a/controller.go
+++ b/controller.go
@@ -15,6 +15,7 @@ import (
 	"google.golang.org/grpc"
 
 	pb "code.fbi.h-da.de/cocsn/api/go/gosdn/core"
+	cpb "code.fbi.h-da.de/cocsn/api/go/gosdn/csbi"
 	ppb "code.fbi.h-da.de/cocsn/api/go/gosdn/pnd"
 	spb "code.fbi.h-da.de/cocsn/api/go/gosdn/southbound"
 	nbi "code.fbi.h-da.de/cocsn/gosdn/northbound/server"
@@ -36,6 +37,8 @@ type Core struct {
 	grpcServer *grpc.Server
 	nbi        *nbi.NorthboundInterface
 	stopChan   chan os.Signal
+
+	csbiClient cpb.CsbiClient
 }
 
 var c *Core
@@ -53,7 +56,7 @@ func init() {
 
 // initialize does start-up housekeeping like reading controller config files
 func initialize() error {
-	if err := startGrpcServer(); err != nil {
+	if err := startGrpc(); err != nil {
 		return err
 	}
 
@@ -63,7 +66,7 @@ func initialize() error {
 	return createSouthboundInterfaces()
 }
 
-func startGrpcServer() error {
+func startGrpc() error {
 	sock := viper.GetString("socket")
 	lis, err := net.Listen("tcp", sock)
 	if err != nil {
@@ -73,11 +76,16 @@ func startGrpcServer() error {
 	c.nbi = nbi.NewNBI(c.pndc)
 	pb.RegisterCoreServer(c.grpcServer, c.nbi.Core)
 	ppb.RegisterPndServer(c.grpcServer, c.nbi.Pnd)
+	cpb.RegisterCsbiServer(c.grpcServer, c.nbi.Csbi)
 	go func() {
 		if err := c.grpcServer.Serve(lis); err != nil {
 			log.Fatal(err)
 		}
 	}()
+
+	orchestrator := viper.GetString("csbi-orchestrator")
+	conn, err := grpc.Dial(orchestrator, grpc.WithInsecure())
+	c.csbiClient = cpb.NewCsbiClient(conn)
 	return nil
 }
 
@@ -89,7 +97,7 @@ func createSouthboundInterfaces() error {
 
 // createPrincipalNetworkDomain initializes the controller with an initial PND
 func createPrincipalNetworkDomain(s nucleus.SouthboundInterface) error {
-	pnd, err := nucleus.NewPND("base", "gosdn base pnd", uuid.New(), s)
+	pnd, err := nucleus.NewPND("base", "gosdn base pnd", uuid.New(), s, c.csbiClient, callback)
 	if err != nil {
 		return err
 	}
@@ -130,3 +138,11 @@ func shutdown() error {
 	c.grpcServer.GracefulStop()
 	return stopHttpServer()
 }
+
+func callback(id uuid.UUID, ch chan nucleus.DeviceDetails) {
+	if ch != nil {
+		c.pndc.AddPendingChannel(id, ch)
+	} else {
+		c.pndc.RemovePendingChannel(id)
+	}
+}
diff --git a/go.mod b/go.mod
index 2822dde48..0d6a64cb7 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module code.fbi.h-da.de/cocsn/gosdn
 go 1.16
 
 require (
-	code.fbi.h-da.de/cocsn/api/go v0.0.0-20210609120033-1ef56612bd26
+	code.fbi.h-da.de/cocsn/api/go v0.0.0-20210609143151-4dabee5ab99a
 	code.fbi.h-da.de/cocsn/yang-models v0.0.7
 	github.com/aristanetworks/goarista v0.0.0-20201120222254-94a892eb0c6a
 	github.com/docker/docker v20.10.6+incompatible
diff --git a/go.sum b/go.sum
index fbaf8d267..4b37f6252 100644
--- a/go.sum
+++ b/go.sum
@@ -21,8 +21,8 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-code.fbi.h-da.de/cocsn/api/go v0.0.0-20210609120033-1ef56612bd26 h1:Uqo/Gfh/pwmkHZFiTtcJGCZbHh+ZDcD2W1zOdABerbI=
-code.fbi.h-da.de/cocsn/api/go v0.0.0-20210609120033-1ef56612bd26/go.mod h1:2+rnE92IyXLbiy3/92EM7JrtsY5tXPAKX90QmsT2+m0=
+code.fbi.h-da.de/cocsn/api/go v0.0.0-20210609143151-4dabee5ab99a h1:C2tdh2A4RckZJXUymtF2ec8hE8mTkhn7EQJShv+i/Jk=
+code.fbi.h-da.de/cocsn/api/go v0.0.0-20210609143151-4dabee5ab99a/go.mod h1:2+rnE92IyXLbiy3/92EM7JrtsY5tXPAKX90QmsT2+m0=
 code.fbi.h-da.de/cocsn/yang-models v0.0.7 h1:3TOo8J+EdAJKeq4o3aaNWZRhjSwguIS8wciW1U9PkSk=
 code.fbi.h-da.de/cocsn/yang-models v0.0.7/go.mod h1:M+2HinfhTT8nA8qvn2cpWNlOtuiizTNDWA3yfy72K/g=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
diff --git a/northbound/server/core.go b/northbound/server/core.go
index 60de83bad..4182c4012 100644
--- a/northbound/server/core.go
+++ b/northbound/server/core.go
@@ -50,7 +50,7 @@ func (s core) Set(ctx context.Context, request *pb.SetRequest) (*pb.SetResponse,
 	for _, r := range request.Pnd {
 		sbi := nucleus.NewSBI(spb.Type_OPENCONFIG)
 
-		pnd, err := nucleus.NewPND(r.Name, r.Description, uuid.New(), sbi)
+		pnd, err := nucleus.NewPND(r.Name, r.Description, uuid.New(), sbi, nil, nil)
 		if err != nil {
 			return nil, err
 		}
diff --git a/northbound/server/csbi.go b/northbound/server/csbi.go
new file mode 100644
index 000000000..f55ece7ce
--- /dev/null
+++ b/northbound/server/csbi.go
@@ -0,0 +1,27 @@
+package server
+
+import (
+	"context"
+	"time"
+
+	cpb "code.fbi.h-da.de/cocsn/api/go/gosdn/csbi"
+	"code.fbi.h-da.de/cocsn/gosdn/nucleus"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+)
+
+type csbi struct {
+	cpb.UnimplementedCsbiServer
+}
+
+func (s csbi) Hello(ctx context.Context, syn *cpb.Syn) (*cpb.Ack, error) {
+	ch, err := pndc.PendingChannels(nucleus.FromString(syn.Id))
+	if err != nil {
+		return nil, status.Errorf(codes.Aborted, "%v", err)
+	}
+	ch <- nucleus.DeviceDetails{
+		ID:      syn.Id,
+		Address: syn.Address,
+	}
+	return &cpb.Ack{Timestamp: time.Now().UnixNano()}, nil
+}
diff --git a/northbound/server/nbi.go b/northbound/server/nbi.go
index 2e17223c9..ac98459b4 100644
--- a/northbound/server/nbi.go
+++ b/northbound/server/nbi.go
@@ -11,6 +11,7 @@ var pndc *nucleus.PndStore
 type NorthboundInterface struct {
 	Pnd  *pnd
 	Core *core
+	Csbi *csbi
 }
 
 // NewNBI receives a PndStore and returns a new gRPC *NorthboundInterface
@@ -19,5 +20,6 @@ func NewNBI(pnds *nucleus.PndStore) *NorthboundInterface {
 	return &NorthboundInterface{
 		Pnd:  &pnd{},
 		Core: &core{},
+		Csbi: &csbi{},
 	}
 }
diff --git a/nucleus/device.go b/nucleus/device.go
index fc74f50e3..9316cbbf7 100644
--- a/nucleus/device.go
+++ b/nucleus/device.go
@@ -1,6 +1,8 @@
 package nucleus
 
 import (
+	"code.fbi.h-da.de/cocsn/gosdn/nucleus/errors"
+
 	tpb "code.fbi.h-da.de/cocsn/api/go/gosdn/transport"
 	"github.com/docker/docker/pkg/namesgenerator"
 	"github.com/google/uuid"
@@ -106,33 +108,33 @@ func (d *CommonDevice) ProcessResponse(resp proto.Message) error {
 
 func (d *CommonDevice) isDevice() {}
 
-type csbiDevice struct {
+type CsbiDevice struct {
 	CommonDevice
 }
 
 // ID returns the UUID of the Device
-func (d *csbiDevice) ID() uuid.UUID {
+func (d *CsbiDevice) ID() uuid.UUID {
 	return d.UUID
 }
 
-func (d *csbiDevice) Model() ygot.GoStruct {
+func (d *CsbiDevice) Model() ygot.GoStruct {
 	return d.GoStruct
 }
 
-func (d *csbiDevice) Transport() Transport {
+func (d *CsbiDevice) Transport() Transport {
 	return d.Transport()
 }
 
-func (d *csbiDevice) Name() string {
+func (d *CsbiDevice) Name() string {
 	return d.Name()
 }
 
-func (d *csbiDevice) SBI() SouthboundInterface {
+func (d *CsbiDevice) SBI() SouthboundInterface {
 	return d.SBI()
 }
 
-func (d *csbiDevice) ProcessResponse(resp proto.Message) error {
-	return d.transport.ProcessResponse(resp, d.GoStruct, d.sbi.Schema())
+func (d *CsbiDevice) ProcessResponse(resp proto.Message) error {
+	return &errors.ErrNotYetImplemented{}
 }
 
-func (d *csbiDevice) isDevice() {}
+func (d *CsbiDevice) isDevice() {}
diff --git a/nucleus/principalNetworkDomain.go b/nucleus/principalNetworkDomain.go
index 7192878c3..7f3db9d65 100644
--- a/nucleus/principalNetworkDomain.go
+++ b/nucleus/principalNetworkDomain.go
@@ -4,7 +4,9 @@ import (
 	"context"
 	"encoding/json"
 	"reflect"
+	"time"
 
+	cpb "code.fbi.h-da.de/cocsn/api/go/gosdn/csbi"
 	ppb "code.fbi.h-da.de/cocsn/api/go/gosdn/pnd"
 	tpb "code.fbi.h-da.de/cocsn/api/go/gosdn/transport"
 
@@ -45,7 +47,7 @@ type PrincipalNetworkDomain interface {
 }
 
 // NewPND creates a Principle Network Domain
-func NewPND(name, description string, id uuid.UUID, sbi SouthboundInterface) (PrincipalNetworkDomain, error) {
+func NewPND(name, description string, id uuid.UUID, sbi SouthboundInterface, c cpb.CsbiClient, callback func(uuid.UUID, chan DeviceDetails)) (PrincipalNetworkDomain, error) {
 	pnd := &pndImplementation{
 		name:             name,
 		description:      description,
@@ -56,6 +58,9 @@ func NewPND(name, description string, id uuid.UUID, sbi SouthboundInterface) (Pr
 		confirmedChanges: ChangeStore{store{}},
 		id:               id,
 		errChans:         make(map[uuid.UUID]chan error),
+
+		csbiClient: c,
+		callback:   callback,
 	}
 	if err := pnd.sbic.Add(sbi); err != nil {
 		return nil, err
@@ -73,6 +78,9 @@ type pndImplementation struct {
 	confirmedChanges ChangeStore
 	id               uuid.UUID
 	errChans         map[uuid.UUID]chan error
+
+	csbiClient cpb.CsbiClient
+	callback   func(uuid.UUID, chan DeviceDetails)
 }
 
 func (pnd *pndImplementation) PendingChanges() []uuid.UUID {
@@ -217,6 +225,10 @@ func (pnd *pndImplementation) RemoveSbi(id uuid.UUID) error {
 
 //AddDevice adds a new device to the PND
 func (pnd *pndImplementation) AddDevice(name string, opt *tpb.TransportOption, sid uuid.UUID) error {
+	if opt.Csbi {
+		return pnd.handleCsbiEnrolment(name, opt)
+	}
+
 	sbi, err := pnd.sbic.Get(sid)
 	if err != nil {
 		return err
@@ -382,3 +394,66 @@ func handleRollbackError(id uuid.UUID, err error) {
 	log.Error(err)
 	// TODO: Notion of invalid state needed.
 }
+
+func (pnd *pndImplementation) handleCsbiEnrolment(name string, opt *tpb.TransportOption) error {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
+	defer cancel()
+	req := &cpb.CreateRequest{
+		Timestamp:       time.Now().UnixNano(),
+		TransportOption: []*tpb.TransportOption{opt},
+	}
+	resp, err := pnd.csbiClient.Create(ctx, req)
+	if err != nil {
+		return err
+	}
+	for _, d := range resp.Deployments {
+		if err := pnd.createCsbiDevice(name, d, opt); err != nil {
+			log.Error(err)
+		}
+	}
+	return nil
+}
+
+type DeviceDetails struct {
+	ID      string
+	Address string
+}
+
+func (pnd *pndImplementation) createCsbiDevice(name string, d *cpb.Deployment, opt *tpb.TransportOption) error {
+	defer func() {
+		if r := recover(); r != nil {
+			log.Error("Recovered in sbi enrolment", r)
+		}
+	}()
+	id, err := uuid.Parse(d.Id)
+	if err != nil {
+		return err
+	}
+	ch := make(chan DeviceDetails, 1)
+	pnd.callback(id, ch)
+	go func() {
+		deviceDetails := <-ch
+		id, err := uuid.Parse(deviceDetails.ID)
+		if err != nil {
+			panic(err)
+		}
+		opt.Address = deviceDetails.Address
+		t, err := NewGnmiTransport(opt, &Csbi{})
+		if err != nil {
+			panic(err)
+		}
+		d := &CsbiDevice{
+			CommonDevice: CommonDevice{
+				name:      name,
+				UUID:      id,
+				transport: t,
+			},
+		}
+		if err := pnd.devices.Add(d, d.name); err != nil {
+			panic(err)
+		}
+		close(ch)
+		pnd.callback(id, nil)
+	}()
+	return nil
+}
diff --git a/nucleus/principalNetworkDomain_test.go b/nucleus/principalNetworkDomain_test.go
index 5e6e5ec43..4780b2b74 100644
--- a/nucleus/principalNetworkDomain_test.go
+++ b/nucleus/principalNetworkDomain_test.go
@@ -48,7 +48,7 @@ func TestNewPND(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := NewPND(tt.args.name, tt.args.description, tt.args.pid, tt.args.sbi)
+			got, err := NewPND(tt.args.name, tt.args.description, tt.args.pid, tt.args.sbi, nil, nil)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("NewPND() error = %v, wantErr %v", err, tt.wantErr)
 				return
diff --git a/nucleus/southbound.go b/nucleus/southbound.go
index 11d63ddb6..5ce1c4716 100644
--- a/nucleus/southbound.go
+++ b/nucleus/southbound.go
@@ -165,3 +165,33 @@ func (oc *OpenConfig) ID() uuid.UUID {
 
 // Type returns the Southbound's type
 func (oc *OpenConfig) Type() spb.Type { return spb.Type_OPENCONFIG }
+
+type Csbi struct {
+	schema *ytypes.Schema
+	id     uuid.UUID
+}
+
+func (csbi *Csbi) SbiIdentifier() string {
+	return "csbi"
+}
+
+func (csbi *Csbi) SetNode() func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error {
+	return func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error {
+		if err := ytypes.SetNode(schema, root.(*Device), path, val, opts...); err != nil {
+			return err
+		}
+		return nil
+	}
+}
+
+func (csbi *Csbi) Schema() *ytypes.Schema {
+	return csbi.schema
+}
+
+func (csbi *Csbi) ID() uuid.UUID {
+	return csbi.id
+}
+
+func (csbi *Csbi) Type() spb.Type {
+	return spb.Type_CONTAINERISED
+}
diff --git a/nucleus/store.go b/nucleus/store.go
index 911ae3112..ec9fd474e 100644
--- a/nucleus/store.go
+++ b/nucleus/store.go
@@ -34,7 +34,9 @@ func NewStore() Store {
 
 // NewPndStore returns a PndStore
 func NewPndStore() *PndStore {
-	return &PndStore{store{}}
+	return &PndStore{
+		store: store{},
+	}
 }
 
 // NewSbiStore returns a SbiStore
@@ -147,6 +149,7 @@ func (s SbiStore) Get(id uuid.UUID) (SouthboundInterface, error) {
 // PndStore is used to store PrincipalNetworkDomains
 type PndStore struct {
 	store
+	pendingChannels map[uuid.UUID]chan DeviceDetails
 }
 
 // Get takes a PrincipalNetworkDomain's UUID and returns the PrincipalNetworkDomain. If the requested
@@ -169,6 +172,22 @@ func (s PndStore) Get(id uuid.UUID) (PrincipalNetworkDomain, error) {
 	return pnd, nil
 }
 
+func (s PndStore) PendingChannels(id uuid.UUID, parseErrors ...error) (chan DeviceDetails, error) {
+	ch, ok := s.pendingChannels[id]
+	if !ok {
+		return nil, &errors.ErrNotFound{ID: id}
+	}
+	return ch, nil
+}
+
+func (s PndStore) AddPendingChannel(id uuid.UUID, ch chan DeviceDetails) {
+	s.pendingChannels[id] = ch
+}
+
+func (s PndStore) RemovePendingChannel(id uuid.UUID) {
+	delete(s.pendingChannels, id)
+}
+
 // DeviceStore is used to store Devices
 type DeviceStore struct {
 	deviceNameToUUIDLookup map[string]uuid.UUID
-- 
GitLab