package api import ( "context" "errors" "time" pb "code.fbi.h-da.de/danet/api/go/gosdn/core" ppb "code.fbi.h-da.de/danet/api/go/gosdn/pnd" spb "code.fbi.h-da.de/danet/api/go/gosdn/southbound" tpb "code.fbi.h-da.de/danet/api/go/gosdn/transport" nbi "code.fbi.h-da.de/danet/gosdn/northbound/client" "github.com/google/uuid" log "github.com/sirupsen/logrus" "github.com/spf13/viper" "google.golang.org/grpc" ) var dialOptions []grpc.DialOption func init() { dialOptions = []grpc.DialOption{ grpc.WithInsecure(), } } // Init initialises the CLI client. func Init(addr string) error { ctx := context.Background() resp, err := getAllCore(ctx, addr) if err != nil { return err } if len(resp.Pnd) > 0 { pid := resp.Pnd[0].Id viper.Set("CLI_PND", pid) log.Infof("PND: %v", pid) if len(resp.Pnd[0].Sbi) != 0 { sbi := resp.Pnd[0].Sbi[0].Id viper.Set("CLI_SBI", sbi) log.Infof("SBI: %v", sbi) } } return viper.WriteConfig() } // GetIds requests all UUID information from the controller func GetIds(addr string) ([]*ppb.PrincipalNetworkDomain, error) { ctx := context.Background() resp, err := getAllCore(ctx, addr) if err != nil { return nil, err } return resp.Pnd, nil } func getAllCore(ctx context.Context, addr string) (*pb.GetResponse, error) { coreClient, err := nbi.CoreClient(addr, dialOptions...) if err != nil { return nil, err } req := &pb.GetRequest{ Timestamp: time.Now().UnixNano(), All: true, } return coreClient.Get(ctx, req) } // AddPnd takes a name, description and SBI UUID to create a new // PrincipalNetworkDomain on the controller func AddPnd(addr, name, description, sbi string) (*pb.SetResponse, error) { coreClient, err := nbi.CoreClient(addr, dialOptions...) if err != nil { return nil, err } ctx := context.Background() req := &pb.SetRequest{ Timestamp: time.Now().UnixNano(), Pnd: []*pb.SetPnd{ { Name: name, Description: description, Sbi: sbi, }, }, } return coreClient.Set(ctx, req) } // GetPnd requests one or several PrincipalNetworkDomains from the // controller. To request all PrincipalNetworkDomains without providing // names or UUIDs use GetIds() func GetPnd(addr string, args ...string) (*pb.GetResponse, error) { coreClient, err := nbi.CoreClient(addr, dialOptions...) if err != nil { return nil, err } if len(args) <= 0 { return nil, errors.New("not enough arguments") } ctx := context.Background() req := &pb.GetRequest{ Timestamp: time.Now().UnixNano(), Pid: args, } return coreClient.Get(ctx, req) } // getChanges requests all pending and unconfirmed changes from the controller func getChanges(addr, pnd string) (*ppb.GetResponse, error) { ctx := context.Background() client, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } req := &ppb.GetRequest{ Timestamp: time.Now().UnixNano(), Request: &ppb.GetRequest_Change{ Change: &ppb.GetChange{ All: true, }, }, Pid: pnd, } return client.Get(ctx, req) } // commit sends a commit request for one or multiple changes to the // controller. func commit(addr, pnd string, cuids ...string) (*ppb.SetResponse, error) { changes := make([]*ppb.SetChange, len(cuids)) for i, arg := range cuids { changes[i] = &ppb.SetChange{ Cuid: arg, Op: ppb.SetChange_COMMIT, } } return commitConfirm(addr, pnd, changes) } // confirm sends a confirm request for one or multiple changes to the // controller func confirm(addr, pnd string, cuids ...string) (*ppb.SetResponse, error) { changes := make([]*ppb.SetChange, len(cuids)) for i, arg := range cuids { changes[i] = &ppb.SetChange{ Cuid: arg, Op: ppb.SetChange_CONFIRM, } } return commitConfirm(addr, pnd, changes) } func commitConfirm(addr, pnd string, changes []*ppb.SetChange) (*ppb.SetResponse, error) { ctx := context.Background() client, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } req := &ppb.SetRequest{ Timestamp: time.Now().UnixNano(), Change: changes, Pid: pnd, } return client.Set(ctx, req) } // addDevice adds a new device to the controller. The device name is optional. // If no name is provided a name will be generated upon device creation. func addDevice(addr, deviceName string, opt *tpb.TransportOption, sid, pid uuid.UUID) (*ppb.SetResponse, error) { pndClient, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } req := &ppb.SetRequest{ Timestamp: time.Now().UnixNano(), Ond: []*ppb.SetOnd{ { Address: opt.GetAddress(), Sbi: &spb.SouthboundInterface{ Id: sid.String(), }, DeviceName: deviceName, TransportOption: opt, }, }, Pid: pid.String(), } if opt.Csbi { req.Ond[0].Sbi.Id = uuid.Nil.String() req.Ond[0].Sbi.Type = spb.Type_CONTAINERISED req.Ond[0].TransportOption.Csbi = true } ctx := context.Background() return pndClient.Set(ctx, req) } // getDevice requests one or multiple devices belonging to a given // PrincipalNetworkDomain from the controller. If no device identifier // is provided, all devices are requested. func getDevice(addr, pid string, did ...string) (*ppb.GetResponse, error) { pndClient, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } var all bool if len(did) == 0 { all = true } req := &ppb.GetRequest{ Timestamp: time.Now().UnixNano(), Request: &ppb.GetRequest_Ond{ Ond: &ppb.GetOnd{ All: all, Did: did, }, }, Pid: pid, } ctx := context.Background() return pndClient.Get(ctx, req) } func getPath(addr, pid, did, path string) (*ppb.GetResponse, error) { pndClient, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } req := &ppb.GetRequest{ Timestamp: time.Now().UnixNano(), Request: &ppb.GetRequest_Path{ Path: &ppb.GetPath{ Did: did, Path: path, }, }, Pid: pid, } ctx := context.Background() return pndClient.Get(ctx, req) } func deleteDevice(addr, pid, did string) (*ppb.DeleteResponse, error) { pndClient, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } req := &ppb.DeleteRequest{ Timestamp: time.Now().UnixNano(), Type: ppb.DeleteRequest_OND, Uuid: did, Pid: pid, } ctx := context.Background() return pndClient.Delete(ctx, req) } // change creates a ChangeRequest for the specified OND. ApiOperations are // used to specify the type of the change (update, replace, delete as specified // in https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#34-modifying-state) // For delete operations the value field needs to contain an empty string. func changeRequest(addr, did, pid, path, value string, op ppb.ApiOperation) (*ppb.SetResponse, error) { req := &ppb.ChangeRequest{ Id: did, Path: path, Value: value, ApiOp: op, } return sendChangeRequest(addr, pid, req) } func sendChangeRequest(addr, pid string, req *ppb.ChangeRequest) (*ppb.SetResponse, error) { pndClient, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } ctx := context.Background() r := &ppb.SetRequest{ Timestamp: time.Now().UnixNano(), ChangeRequest: []*ppb.ChangeRequest{req}, Pid: pid, } return pndClient.Set(ctx, r) }