diff --git a/cli/grpc.go b/cli/grpc.go index 3d731ab907d23c7f5c7dfdb567c72e655d2975c8..1bd0218e0a5591bf68e7ea614d3d9e31b2fe3c61 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 da943ff1256ceb4701dccf4b98a1bfe87a630b10..bd8805e333d17265ce64af45351a19b21c8e94cc 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 2822dde484ee837498712dffd35a507c909c945a..0d6a64cb7c029090e165945e8994a48f8aa630af 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 fbaf8d2673f3a12dba8183eaa7d8ccfdf98ad892..4b37f6252964d6a8f8535f6394fa2a359bdfeb9c 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 60de83baded00b37a2c2d84166913f5b18cc7c8f..4182c4012ce6a5665edf82a03cff7445e4da0fef 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 0000000000000000000000000000000000000000..f55ece7ce12901ad0074645f939adcefa7ec21e7 --- /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 2e17223c969be515b278f07e6c02775b4e761dc7..ac98459b4eafad5931669fa842f8d2cbd2f7dc29 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 fc74f50e3f845c66f61287a892b064229a202a3c..9316cbbf729d0fc031f9200d189847657f8f1cc8 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 7192878c39b172d92200c88b2d1290eebd1eb2c1..7f3db9d65fdca6c144e143a92f87f8d08811a29f 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 5e6e5ec43e6c9463447aed72f703df0a398d7d5e..4780b2b74ed9df47812fec9914531649730c95b6 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 11d63ddb6e537c78d6e0d9ecb29652efd53df77f..5ce1c47165d81a804de4977d7043526c549ffb3c 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 911ae31129a32358660e52afb9a14d4125983d84..ec9fd474ed859ac8ce05bdf7ff3a441371f58a9f 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