diff --git a/dataplane/saiserver/routing.go b/dataplane/saiserver/routing.go index b6f93e870..e30547d01 100644 --- a/dataplane/saiserver/routing.go +++ b/dataplane/saiserver/routing.go @@ -296,6 +296,59 @@ func (nhg *nextHopGroup) updateNextHopGroupMember(ctx context.Context, nhgid, mi return err } +// SetNextHopGroupAttribute sets the attribute of the next hop group. +func (nhg *nextHopGroup) SetNextHopGroupAttribute(ctx context.Context, req *saipb.SetNextHopGroupAttributeRequest) (*saipb.SetNextHopGroupAttributeResponse, error) { + if _, ok := nhg.groups[req.GetOid()]; !ok { + return nil, status.Errorf(codes.NotFound, "group %d does not exist", req.GetOid()) + } + resp := &saipb.GetNextHopGroupAttributeResponse{ + Attr: &saipb.NextHopGroupAttribute{}, + } + pBytes, err := proto.Marshal(req) + if err != nil { + return nil, err + } + if err := nhg.mgr.PopulateAllAttributes(string(pBytes), resp.Attr); err != nil { + return nil, err + } + + // If next hop list is set, then replace all members. + if len(req.GetNextHopList()) > 0 { + attr := &saipb.NextHopGroupAttribute{} + if err := nhg.mgr.PopulateAllAttributes(fmt.Sprint(req.GetOid()), attr); err != nil { + return nil, err + } + attr.NextHopList = req.GetNextHopList() + attr.NextHopMemberWeightList = nil + + for mid := range nhg.groups[req.GetOid()] { // Create a copy of the keys since we are deleting them. + if _, err := nhg.RemoveNextHopGroupMember(ctx, &saipb.RemoveNextHopGroupMemberRequest{Oid: mid}); err != nil { + return nil, err + } + } + memReq := &saipb.CreateNextHopGroupMembersRequest{} + for i, nh := range req.GetNextHopList() { + weight := uint32(1) + if len(req.GetNextHopMemberWeightList()) > i { + weight = req.GetNextHopMemberWeightList()[i] + } + attr.NextHopMemberWeightList = append(attr.NextHopMemberWeightList, weight) + memReq.Reqs = append(memReq.Reqs, &saipb.CreateNextHopGroupMemberRequest{ + Switch: switchID, + NextHopGroupId: proto.Uint64(req.GetOid()), + NextHopId: proto.Uint64(nh), + Weight: proto.Uint32(weight), + }) + } + if _, err := attrmgr.InvokeAndSave(ctx, nhg.mgr, nhg.CreateNextHopGroupMembers, memReq); err != nil { + return nil, err + } + nhg.mgr.StoreAttributes(req.GetOid(), attr) + } + + return &saipb.SetNextHopGroupAttributeResponse{}, nil +} + // RemoveNextHopGroup removes the next hop group specified in the OID. func (nhg *nextHopGroup) RemoveNextHopGroup(_ context.Context, req *saipb.RemoveNextHopGroupRequest) (*saipb.RemoveNextHopGroupResponse, error) { oid := req.GetOid() diff --git a/dataplane/saiserver/routing_test.go b/dataplane/saiserver/routing_test.go index 34f809113..788b5bed1 100644 --- a/dataplane/saiserver/routing_test.go +++ b/dataplane/saiserver/routing_test.go @@ -17,6 +17,7 @@ package saiserver import ( "context" "encoding/binary" + "fmt" "testing" "github.com/google/go-cmp/cmp" @@ -181,6 +182,71 @@ func TestRemoveNextHopGroup(t *testing.T) { } } +func TestSetNextHopGroupAttribute(t *testing.T) { + tests := []struct { + desc string + req *saipb.SetNextHopGroupAttributeRequest + wantAttr *saipb.NextHopGroupAttribute + wantErr string + }{{ + desc: "replace members", + req: &saipb.SetNextHopGroupAttributeRequest{ + NextHopList: []uint64{3}, + }, + wantAttr: &saipb.NextHopGroupAttribute{ + NextHopList: []uint64{3}, + NextHopMemberWeightList: []uint32{1}, + Type: saipb.NextHopGroupType_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP.Enum(), + }, + }} + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + dplane := &fakeSwitchDataplane{} + c, mgr, stopFn := newTestNextHopGroup(t, dplane) + defer stopFn() + + mgr.StoreAttributes(mgr.NextID(), &saipb.SwitchAttribute{EcmpHashIpv4: proto.Uint64(10), EcmpHashIpv6: proto.Uint64(9)}) + mgr.StoreAttributes(3, &saipb.CreateNextHopRequest{Ip: []byte{127, 0, 0, 1}}) + mgr.StoreAttributes(4, &saipb.CreateNextHopRequest{Ip: []byte{127, 0, 0, 2}}) + mgr.StoreAttributes(10, &saipb.CreateHashRequest{ + NativeHashFieldList: []saipb.NativeHashField{saipb.NativeHashField_NATIVE_HASH_FIELD_DST_IP}, + }) + + ctx := context.Background() + resp, err := c.CreateNextHopGroup(ctx, &saipb.CreateNextHopGroupRequest{Type: saipb.NextHopGroupType_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP.Enum()}) + if err != nil { + t.Fatal(err) + } + tt.req.Oid = resp.Oid + + _, err = c.CreateNextHopGroupMember(ctx, &saipb.CreateNextHopGroupMemberRequest{ + NextHopGroupId: proto.Uint64(resp.Oid), + NextHopId: proto.Uint64(4), + Weight: proto.Uint32(1), + }) + if err != nil { + t.Fatal(err) + } + + _, gotErr := c.SetNextHopGroupAttribute(ctx, tt.req) + if diff := errdiff.Check(gotErr, tt.wantErr); diff != "" { + t.Fatalf("SetNextHopGroupAttribute() unexpected err: %s", diff) + } + if gotErr != nil { + return + } + + attr := &saipb.NextHopGroupAttribute{} + if err := mgr.PopulateAllAttributes(fmt.Sprint(resp.Oid), attr); err != nil { + t.Fatal(err) + } + if d := cmp.Diff(attr, tt.wantAttr, protocmp.Transform()); d != "" { + t.Errorf("SetNextHopGroupAttribute() failed: diff(-got,+want)\n:%s", d) + } + }) + } +} + func TestCreateNextHopGroupMember(t *testing.T) { tests := []struct { desc string diff --git a/dataplane/standalone/sai/next_hop_group.cc b/dataplane/standalone/sai/next_hop_group.cc index fb9a02a0a..ff51b6414 100644 --- a/dataplane/standalone/sai/next_hop_group.cc +++ b/dataplane/standalone/sai/next_hop_group.cc @@ -730,7 +730,13 @@ sai_status_t l_remove_next_hop_groups(uint32_t object_count, const sai_object_id } sai_status_t l_set_next_hop_groups_attribute(uint32_t object_count, const sai_object_id_t *object_id, const sai_attribute_t *attr_list, sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses) { - LOG(INFO) << "Func: " << __PRETTY_FUNCTION__ << " is not implemented but by-passing check"; + LOG(INFO) << "Func: " << __PRETTY_FUNCTION__; + for (uint32_t i = 0; i < object_count; i++) { + object_statuses[i] = l_set_next_hop_group_attribute(object_id[i], &attr_list[i]); + if (object_statuses[i] != SAI_STATUS_SUCCESS && mode == SAI_BULK_OP_ERROR_MODE_STOP_ON_ERROR) { + return SAI_STATUS_FAILURE; + } + } return SAI_STATUS_SUCCESS; }