package api import ( "context" "errors" "io" "time" pb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/core" ppb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd" apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac" spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound" tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport" nbi "code.fbi.h-da.de/danet/gosdn/controller/northbound/client" "github.com/google/uuid" "github.com/openconfig/goyang/pkg/yang" "github.com/openconfig/ygot/ygot" log "github.com/sirupsen/logrus" "github.com/spf13/viper" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) var dialOptions []grpc.DialOption func init() { dialOptions = []grpc.DialOption{ grpc.WithTransportCredentials(insecure.NewCredentials()), } } // 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 } // GetAllCore requests all PNDs func GetAllCore(ctx context.Context, addr string) (*pb.GetPndListResponse, error) { coreClient, err := nbi.CoreClient(addr, dialOptions...) if err != nil { return nil, err } req := &pb.GetPndListRequest{ Timestamp: time.Now().UnixNano(), } return coreClient.GetPndList(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.CreatePndListResponse, error) { coreClient, err := nbi.CoreClient(addr, dialOptions...) if err != nil { return nil, err } ctx := context.Background() req := &pb.CreatePndListRequest{ Timestamp: time.Now().UnixNano(), Pnd: []*pb.PndCreateProperties{ { Name: name, Description: description, Sbi: sbi, }, }, } return coreClient.CreatePndList(ctx, req) } // GetPnd requests one PrincipalNetworkDomain from the // controller. func GetPnd(addr string, args ...string) (*pb.GetPndResponse, 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.GetPndRequest{ Timestamp: time.Now().UnixNano(), Pid: args, } return coreClient.GetPnd(ctx, req) } // GetPnds requests all PrincipalNetworkDomains from the // controller. func GetPnds(addr string, args ...string) (*pb.GetPndListResponse, 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.GetPndListRequest{ Timestamp: time.Now().UnixNano(), } return coreClient.GetPndList(ctx, req) } // DeletePnd requests a deletion of the provided PND. func DeletePnd(addr string, pid string) (*pb.DeletePndResponse, error) { coreClient, err := nbi.CoreClient(addr, dialOptions...) if err != nil { return nil, err } ctx := context.Background() req := &pb.DeletePndRequest{ Timestamp: time.Now().UnixNano(), Pid: pid, } return coreClient.DeletePnd(ctx, req) } // GetSbi requests one or more to the provided PND belonging SBIs from the // controller. func GetSbi(addr string, pid string, sid ...string) (*ppb.GetSbiResponse, error) { client, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } ctx := context.Background() req := &ppb.GetSbiRequest{ Timestamp: time.Now().UnixNano(), Pid: pid, Sid: sid, } return client.GetSbi(ctx, req) } //GetSBIs requests all to the provided PND belonging SBIs from the controller. func GetSBIs(addr string, pid string) (*ppb.GetSbiListResponse, error) { client, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } ctx := context.Background() req := &ppb.GetSbiListRequest{ Timestamp: time.Now().UnixNano(), Pid: pid, } return client.GetSbiList(ctx, req) } // GetChanges requests all pending and unconfirmed changes from the controller func GetChanges(addr, pnd string) (*ppb.GetChangeListResponse, error) { ctx := context.Background() client, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } req := &ppb.GetChangeListRequest{ Timestamp: time.Now().UnixNano(), Pid: pnd, } return client.GetChangeList(ctx, req) } // Commit sends a Commit request for one or multiple changes to the // controller. func Commit(addr, pnd string, cuids ...string) (*ppb.SetChangeListResponse, error) { changes := make([]*ppb.SetChange, len(cuids)) for i, arg := range cuids { changes[i] = &ppb.SetChange{ Cuid: arg, Op: ppb.Operation_OPERATION_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.SetChangeListResponse, error) { changes := make([]*ppb.SetChange, len(cuids)) for i, arg := range cuids { changes[i] = &ppb.SetChange{ Cuid: arg, Op: ppb.Operation_OPERATION_CONFIRM, } } return CommitConfirm(addr, pnd, changes) } // CommitConfirm confirms a commit func CommitConfirm(addr, pnd string, changes []*ppb.SetChange) (*ppb.SetChangeListResponse, error) { ctx := context.Background() client, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } req := &ppb.SetChangeListRequest{ Timestamp: time.Now().UnixNano(), Change: changes, Pid: pnd, } return client.SetChangeList(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.SetOndListResponse, error) { pndClient, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } req := &ppb.SetOndListRequest{ Timestamp: time.Now().UnixNano(), Ond: []*ppb.SetOnd{ { Address: opt.GetAddress(), Sbi: &spb.SouthboundInterface{ Id: sid.String(), }, DeviceName: deviceName, TransportOption: opt, }, }, Pid: pid.String(), } switch t := opt.Type; t { case spb.Type_TYPE_CONTAINERISED, spb.Type_TYPE_PLUGIN: req.Ond[0].Sbi.Id = uuid.Nil.String() req.Ond[0].Sbi.Type = t req.Ond[0].TransportOption.Type = t default: } ctx := context.Background() return pndClient.SetOndList(ctx, req) } // GetDevice requests one device belonging to a given // PrincipalNetworkDomain from the controller. If no device identifier // is provided, an error is thrown. func GetDevice(addr, pid string, did ...string) (*ppb.GetOndResponse, error) { pndClient, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } if len(did) == 0 { return nil, err } req := &ppb.GetOndRequest{ Timestamp: time.Now().UnixNano(), Did: did, Pid: pid, } ctx := context.Background() return pndClient.GetOnd(ctx, req) } // GetSbiSchemaTree gets the sbi tree for a sbi func GetSbiSchemaTree(addr string, pid, sid uuid.UUID) (map[string]*yang.Entry, error) { sbiClient, err := nbi.SbiClient(addr, dialOptions...) if err != nil { return map[string]*yang.Entry{}, err } req := &spb.GetSchemaRequest{ Timestamp: time.Now().UnixNano(), Pid: pid.String(), Sid: sid.String(), } ctx, cancel := context.WithTimeout(context.Background(), time.Minute*10) defer cancel() sClient, err := sbiClient.GetSchema(ctx, req) if err != nil { return map[string]*yang.Entry{}, err } sTreeBytes := []byte{} for { payload, err := sClient.Recv() if err != nil { if err == io.EOF { break } log.Error(err) sClient.CloseSend() return map[string]*yang.Entry{}, err } sTreeBytes = append(sTreeBytes, payload.Chunk...) } sTreeMap, err := ygot.GzipToSchema(sTreeBytes) if err != nil { return map[string]*yang.Entry{}, err } return sTreeMap, nil } // GetDevices requests all devices belonging to a given // PrincipalNetworkDomain from the controller. func GetDevices(addr, pid string) (*ppb.GetOndListResponse, error) { pndClient, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } req := &ppb.GetOndListRequest{ Timestamp: time.Now().UnixNano(), Pid: pid, } ctx := context.Background() return pndClient.GetOndList(ctx, req) } // GetPath requests a specific path func GetPath(addr, pid, did, path string) (*ppb.GetPathResponse, error) { pndClient, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } req := &ppb.GetPathRequest{ Timestamp: time.Now().UnixNano(), Did: did, Pid: pid, Path: path, } ctx := context.Background() return pndClient.GetPath(ctx, req) } // DeleteDevice deletes a device func DeleteDevice(addr, pid, did string) (*ppb.DeleteOndResponse, error) { pndClient, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } req := &ppb.DeleteOndRequest{ Timestamp: time.Now().UnixNano(), Did: did, Pid: pid, } ctx := context.Background() return pndClient.DeleteOnd(ctx, req) } // ChangeRequest 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.SetPathListResponse, error) { req := &ppb.ChangeRequest{ Did: did, Path: path, Value: value, ApiOp: op, } return SendChangeRequest(addr, pid, req) } // SendChangeRequest sends a change request func SendChangeRequest(addr, pid string, req *ppb.ChangeRequest) (*ppb.SetPathListResponse, error) { pndClient, err := nbi.PndClient(addr, dialOptions...) if err != nil { return nil, err } ctx := context.Background() r := &ppb.SetPathListRequest{ Timestamp: time.Now().UnixNano(), ChangeRequest: []*ppb.ChangeRequest{req}, Pid: pid, } return pndClient.SetPathList(ctx, r) } // Login logs a user in func Login(addr, username, pwd string) (*apb.LoginResponse, error) { authClient, err := nbi.AuthClient(addr, dialOptions...) if err != nil { return nil, err } ctx := context.Background() r := &apb.LoginRequest{ Timestamp: time.Now().UnixNano(), Username: username, Pwd: pwd, } return authClient.Login(ctx, r) } // Logout logs a user out func Logout(addr, username string) (*apb.LogoutResponse, error) { authClient, err := nbi.AuthClient(addr, dialOptions...) if err != nil { return nil, err } ctx := context.Background() r := &apb.LogoutRequest{ Timestamp: time.Now().UnixNano(), Username: username, } return authClient.Logout(ctx, r) }