From 71379e557df1718d439a8677fedbdd7373c54a50 Mon Sep 17 00:00:00 2001 From: Boris Glimcher Date: Thu, 7 Sep 2023 05:33:12 +0300 Subject: [PATCH] feat(list): add pagination to list funcs Signed-off-by: Boris Glimcher --- pkg/evpn/bridge.go | 13 ++++++++- pkg/evpn/bridge_test.go | 16 +++++++++++ pkg/evpn/evpn.go | 63 ++++++++++++++++++++++++++++++++++------- pkg/evpn/port.go | 13 ++++++++- pkg/evpn/port_test.go | 16 +++++++++++ pkg/evpn/svi.go | 13 ++++++++- pkg/evpn/svi_test.go | 16 +++++++++++ pkg/evpn/vrf.go | 12 ++++++++ pkg/evpn/vrf_test.go | 16 +++++++++++ 9 files changed, 165 insertions(+), 13 deletions(-) diff --git a/pkg/evpn/bridge.go b/pkg/evpn/bridge.go index f2334b58..9d558f9e 100644 --- a/pkg/evpn/bridge.go +++ b/pkg/evpn/bridge.go @@ -253,14 +253,25 @@ func (s *Server) ListLogicalBridges(_ context.Context, in *pb.ListLogicalBridges log.Printf("error: %v", err) return nil, err } + // fetch object from the database + size, offset, perr := extractPagination(in.PageSize, in.PageToken, s.Pagination) + if perr != nil { + log.Printf("error: %v", perr) + return nil, perr + } token := "" + log.Printf("Limiting result len(%d) to [%d:%d]", len(s.Bridges), offset, size) + // result, hasMoreElements := limitPagination(s.Bridges, offset, size) + // if hasMoreElements { + // token = uuid.New().String() + // s.Pagination[token] = offset + size + // } Blobarray := []*pb.LogicalBridge{} for _, bridge := range s.Bridges { r := protoClone(bridge) r.Status = &pb.LogicalBridgeStatus{OperStatus: pb.LBOperStatus_LB_OPER_STATUS_UP} Blobarray = append(Blobarray, r) } - // TODO: Limit results to offset and size and rememeber pagination sortLogicalBridges(Blobarray) return &pb.ListLogicalBridgesResponse{LogicalBridges: Blobarray, NextPageToken: token}, nil } diff --git a/pkg/evpn/bridge_test.go b/pkg/evpn/bridge_test.go index 05dd9638..1667386a 100644 --- a/pkg/evpn/bridge_test.go +++ b/pkg/evpn/bridge_test.go @@ -651,6 +651,22 @@ func Test_ListLogicalBridges(t *testing.T) { size: 0, token: "", }, + "pagination negative": { + in: "", + out: nil, + errCode: codes.InvalidArgument, + errMsg: "negative PageSize is not allowed", + size: -10, + token: "", + }, + "pagination error": { + in: "", + out: nil, + errCode: codes.NotFound, + errMsg: fmt.Sprintf("unable to find pagination token %s", "unknown-pagination-token"), + size: 0, + token: "unknown-pagination-token", + }, } // run tests diff --git a/pkg/evpn/evpn.go b/pkg/evpn/evpn.go index b8e38b81..b03f4852 100644 --- a/pkg/evpn/evpn.go +++ b/pkg/evpn/evpn.go @@ -10,6 +10,8 @@ import ( "fmt" "log" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" pe "github.com/opiproject/opi-api/network/evpn-gw/v1alpha1/gen/go" @@ -27,11 +29,12 @@ type Server struct { pe.UnimplementedSviServiceServer pe.UnimplementedLogicalBridgeServiceServer pe.UnimplementedBridgePortServiceServer - Bridges map[string]*pe.LogicalBridge - Ports map[string]*pe.BridgePort - Svis map[string]*pe.Svi - Vrfs map[string]*pe.Vrf - nLink utils.Netlink + Bridges map[string]*pe.LogicalBridge + Ports map[string]*pe.BridgePort + Svis map[string]*pe.Svi + Vrfs map[string]*pe.Vrf + Pagination map[string]int + nLink utils.Netlink } // NewServer creates initialized instance of EVPN server @@ -47,11 +50,12 @@ func NewServerWithArgs(nLink utils.Netlink) *Server { log.Panic("nil for Netlink is not allowed") } return &Server{ - Bridges: make(map[string]*pe.LogicalBridge), - Ports: make(map[string]*pe.BridgePort), - Svis: make(map[string]*pe.Svi), - Vrfs: make(map[string]*pe.Vrf), - nLink: nLink, + Bridges: make(map[string]*pe.LogicalBridge), + Ports: make(map[string]*pe.BridgePort), + Svis: make(map[string]*pe.Svi), + Vrfs: make(map[string]*pe.Vrf), + Pagination: make(map[string]int), + nLink: nLink, } } @@ -74,3 +78,42 @@ func generateRandMAC() ([]byte, error) { return buf, nil } + +func extractPagination(pageSize int32, pageToken string, pagination map[string]int) (size int, offset int, err error) { + const ( + maxPageSize = 250 + defaultPageSize = 50 + ) + switch { + case pageSize < 0: + return -1, -1, status.Error(codes.InvalidArgument, "negative PageSize is not allowed") + case pageSize == 0: + size = defaultPageSize + case pageSize > maxPageSize: + size = maxPageSize + default: + size = int(pageSize) + } + // fetch offset from the database using opaque token + offset = 0 + if pageToken != "" { + var ok bool + offset, ok = pagination[pageToken] + if !ok { + return -1, -1, status.Errorf(codes.NotFound, "unable to find pagination token %s", pageToken) + } + log.Printf("Found offset %d from pagination token: %s", offset, pageToken) + } + return size, offset, nil +} + +// func limitPagination[T any](result []T, offset int, size int) ([]T, bool) { +// end := offset + size +// hasMoreElements := false +// if end < len(result) { +// hasMoreElements = true +// } else { +// end = len(result) +// } +// return result[offset:end], hasMoreElements +// } diff --git a/pkg/evpn/port.go b/pkg/evpn/port.go index cb264a6f..97b7d122 100644 --- a/pkg/evpn/port.go +++ b/pkg/evpn/port.go @@ -278,14 +278,25 @@ func (s *Server) ListBridgePorts(_ context.Context, in *pb.ListBridgePortsReques log.Printf("error: %v", err) return nil, err } + // fetch object from the database + size, offset, perr := extractPagination(in.PageSize, in.PageToken, s.Pagination) + if perr != nil { + log.Printf("error: %v", perr) + return nil, perr + } token := "" + log.Printf("Limiting result len(%d) to [%d:%d]", len(s.Ports), offset, size) + // result, hasMoreElements := limitPagination(s.Ports, offset, size) + // if hasMoreElements { + // token = uuid.New().String() + // s.Pagination[token] = offset + size + // } Blobarray := []*pb.BridgePort{} for _, port := range s.Ports { r := protoClone(port) r.Status = &pb.BridgePortStatus{OperStatus: pb.BPOperStatus_BP_OPER_STATUS_UP} Blobarray = append(Blobarray, r) } - // TODO: Limit results to offset and size and rememeber pagination sortBridgePorts(Blobarray) return &pb.ListBridgePortsResponse{BridgePorts: Blobarray, NextPageToken: token}, nil } diff --git a/pkg/evpn/port_test.go b/pkg/evpn/port_test.go index 33face01..3cf7c50f 100644 --- a/pkg/evpn/port_test.go +++ b/pkg/evpn/port_test.go @@ -684,6 +684,22 @@ func Test_ListBridgePorts(t *testing.T) { size: 0, token: "", }, + "pagination negative": { + in: "", + out: nil, + errCode: codes.InvalidArgument, + errMsg: "negative PageSize is not allowed", + size: -10, + token: "", + }, + "pagination error": { + in: "", + out: nil, + errCode: codes.NotFound, + errMsg: fmt.Sprintf("unable to find pagination token %s", "unknown-pagination-token"), + size: 0, + token: "unknown-pagination-token", + }, } // run tests diff --git a/pkg/evpn/svi.go b/pkg/evpn/svi.go index ed7382ee..cbc1a69a 100644 --- a/pkg/evpn/svi.go +++ b/pkg/evpn/svi.go @@ -311,14 +311,25 @@ func (s *Server) ListSvis(_ context.Context, in *pb.ListSvisRequest) (*pb.ListSv log.Printf("error: %v", err) return nil, err } + // fetch object from the database + size, offset, perr := extractPagination(in.PageSize, in.PageToken, s.Pagination) + if perr != nil { + log.Printf("error: %v", perr) + return nil, perr + } token := "" + log.Printf("Limiting result len(%d) to [%d:%d]", len(s.Svis), offset, size) + // result, hasMoreElements := limitPagination(s.Svis, offset, size) + // if hasMoreElements { + // token = uuid.New().String() + // s.Pagination[token] = offset + size + // } Blobarray := []*pb.Svi{} for _, svi := range s.Svis { r := protoClone(svi) r.Status = &pb.SviStatus{OperStatus: pb.SVIOperStatus_SVI_OPER_STATUS_UP} Blobarray = append(Blobarray, r) } - // TODO: Limit results to offset and size and rememeber pagination sortSvis(Blobarray) return &pb.ListSvisResponse{Svis: Blobarray, NextPageToken: token}, nil } diff --git a/pkg/evpn/svi_test.go b/pkg/evpn/svi_test.go index 7b9208c7..3add299c 100644 --- a/pkg/evpn/svi_test.go +++ b/pkg/evpn/svi_test.go @@ -810,6 +810,22 @@ func Test_ListSvis(t *testing.T) { size: 0, token: "", }, + "pagination negative": { + in: "", + out: nil, + errCode: codes.InvalidArgument, + errMsg: "negative PageSize is not allowed", + size: -10, + token: "", + }, + "pagination error": { + in: "", + out: nil, + errCode: codes.NotFound, + errMsg: fmt.Sprintf("unable to find pagination token %s", "unknown-pagination-token"), + size: 0, + token: "unknown-pagination-token", + }, } // run tests diff --git a/pkg/evpn/vrf.go b/pkg/evpn/vrf.go index c3c73525..8cf0a3d5 100644 --- a/pkg/evpn/vrf.go +++ b/pkg/evpn/vrf.go @@ -326,7 +326,19 @@ func (s *Server) ListVrfs(_ context.Context, in *pb.ListVrfsRequest) (*pb.ListVr log.Printf("error: %v", err) return nil, err } + // fetch object from the database + size, offset, perr := extractPagination(in.PageSize, in.PageToken, s.Pagination) + if perr != nil { + log.Printf("error: %v", perr) + return nil, perr + } token := "" + log.Printf("Limiting result len(%d) to [%d:%d]", len(s.Vrfs), offset, size) + // result, hasMoreElements := limitPagination(s.Vrfs, offset, size) + // if hasMoreElements { + // token = uuid.New().String() + // s.Pagination[token] = offset + size + // } Blobarray := []*pb.Vrf{} for _, vrf := range s.Vrfs { r := protoClone(vrf) diff --git a/pkg/evpn/vrf_test.go b/pkg/evpn/vrf_test.go index 6f669bac..ca3bfcae 100644 --- a/pkg/evpn/vrf_test.go +++ b/pkg/evpn/vrf_test.go @@ -842,6 +842,22 @@ func Test_ListVrfs(t *testing.T) { size: 0, token: "", }, + "pagination negative": { + in: "", + out: nil, + errCode: codes.InvalidArgument, + errMsg: "negative PageSize is not allowed", + size: -10, + token: "", + }, + "pagination error": { + in: "", + out: nil, + errCode: codes.NotFound, + errMsg: fmt.Sprintf("unable to find pagination token %s", "unknown-pagination-token"), + size: 0, + token: "unknown-pagination-token", + }, } // run tests