blob: f2456ac29d39af03270ed7eee103a893447cfbd6 [file] [log] [blame]
Girish Gowdra9602eb42020-09-09 15:50:39 -07001/*
Joey Armstrong11f5a572024-01-12 19:11:32 -05002 * Copyright 2020-2024 Open Networking Foundation (ONF) and the ONF Contributors
Girish Gowdra9602eb42020-09-09 15:50:39 -07003 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 * http://www.apache.org/licenses/LICENSE-2.0
7 * Unless required by applicable law or agreed to in writing, software
8 * distributed under the License is distributed on an "AS IS" BASIS,
9 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 * See the License for the specific language governing permissions and
11 * limitations under the License.
12 */
13
Joey Armstrong3f0e2422023-07-05 18:25:41 -040014// Package core provides the utility for olt devices, flows, groups and statistics
Girish Gowdra9602eb42020-09-09 15:50:39 -070015package core
16
17import (
18 "context"
khenaidoo106c61a2021-08-11 18:05:46 -040019 "sync"
20
21 "github.com/opencord/voltha-lib-go/v7/pkg/flows"
22 "github.com/opencord/voltha-lib-go/v7/pkg/log"
Mahir Gunyel85f61c12021-10-06 11:53:45 -070023 plt "github.com/opencord/voltha-lib-go/v7/pkg/platform"
Girish Gowdra9602eb42020-09-09 15:50:39 -070024 "github.com/opencord/voltha-openolt-adapter/internal/pkg/olterrors"
25 rsrcMgr "github.com/opencord/voltha-openolt-adapter/internal/pkg/resourcemanager"
khenaidoo106c61a2021-08-11 18:05:46 -040026 ofp "github.com/opencord/voltha-protos/v5/go/openflow_13"
27 openoltpb2 "github.com/opencord/voltha-protos/v5/go/openolt"
Girish Gowdra9602eb42020-09-09 15:50:39 -070028 "google.golang.org/grpc/codes"
29 "google.golang.org/grpc/status"
Girish Gowdra9602eb42020-09-09 15:50:39 -070030)
31
Joey Armstrong3f0e2422023-07-05 18:25:41 -040032// QueueInfoBrief has information about gemPortID and service priority associated with Mcast group
Girish Gowdra9602eb42020-09-09 15:50:39 -070033type QueueInfoBrief struct {
34 gemPortID uint32
35 servicePriority uint32
36}
37
Joey Armstrong3f0e2422023-07-05 18:25:41 -040038// OpenOltGroupMgr creates the Structure of OpenOltGroupMgr obj
Girish Gowdra9602eb42020-09-09 15:50:39 -070039type OpenOltGroupMgr struct {
40 deviceHandler *DeviceHandler
41 resourceMgr *rsrcMgr.OpenOltResourceMgr
42 interfaceToMcastQueueMap map[uint32]*QueueInfoBrief /*pon interface -> multicast queue map. Required to assign GEM to a bucket during group population*/
43 interfaceToMcastQueueMapLock sync.RWMutex
44}
45
46//////////////////////////////////////////////
47// EXPORTED FUNCTIONS //
48//////////////////////////////////////////////
49
Joey Armstrong3f0e2422023-07-05 18:25:41 -040050// NewGroupManager creates OpenOltGroupMgr object and initializes the parameters
Girish Gowdra9602eb42020-09-09 15:50:39 -070051func NewGroupManager(ctx context.Context, dh *DeviceHandler, rMgr *rsrcMgr.OpenOltResourceMgr) *OpenOltGroupMgr {
Girish Gowdra4736e5c2021-08-25 15:19:10 -070052 logger.Infow(ctx, "initializing-group-manager", log.Fields{"device-id": dh.device.Id})
Girish Gowdra9602eb42020-09-09 15:50:39 -070053 var grpMgr OpenOltGroupMgr
54 grpMgr.deviceHandler = dh
55 grpMgr.resourceMgr = rMgr
56 grpMgr.interfaceToMcastQueueMap = make(map[uint32]*QueueInfoBrief)
57 grpMgr.interfaceToMcastQueueMapLock = sync.RWMutex{}
58 logger.Info(ctx, "initialization-of-group-manager-success")
59 return &grpMgr
60}
61
62// AddGroup add or update the group
63func (g *OpenOltGroupMgr) AddGroup(ctx context.Context, group *ofp.OfpGroupEntry) error {
64 logger.Infow(ctx, "add-group", log.Fields{"group": group})
65 if group == nil {
66 return olterrors.NewErrInvalidValue(log.Fields{"group": group}, nil)
67 }
68 groupToOlt := openoltpb2.Group{
69 GroupId: group.Desc.GroupId,
70 Command: openoltpb2.Group_SET_MEMBERS,
71 Action: g.buildGroupAction(),
72 }
73 logger.Debugw(ctx, "sending-group-to-device", log.Fields{"groupToOlt": groupToOlt})
bseenivaa1622112025-12-11 18:24:02 +053074 subCtx, cancel := context.WithTimeout(log.WithSpanFromContext(context.Background(), ctx), g.deviceHandler.cfg.RPCTimeout)
75 _, err := g.deviceHandler.Client.PerformGroupOperation(subCtx, &groupToOlt)
76 cancel()
Girish Gowdra9602eb42020-09-09 15:50:39 -070077 if err != nil {
78 return olterrors.NewErrAdapter("add-group-operation-failed", log.Fields{"groupToOlt": groupToOlt}, err)
79 }
80 // group members not created yet. So let's store the group
81 if err := g.resourceMgr.AddFlowGroupToKVStore(ctx, group, true); err != nil {
Girish Gowdraa09aeab2020-09-14 16:30:52 -070082 return olterrors.NewErrPersistence("add", "flow-group", uint64(group.Desc.GroupId), log.Fields{"group": group}, err)
Girish Gowdra9602eb42020-09-09 15:50:39 -070083 }
84 logger.Infow(ctx, "add-group-operation-performed-on-the-device-successfully ", log.Fields{"groupToOlt": groupToOlt})
85 return nil
86}
87
88// DeleteGroup deletes a group from the device
89func (g *OpenOltGroupMgr) DeleteGroup(ctx context.Context, group *ofp.OfpGroupEntry) error {
90 logger.Debugw(ctx, "delete-group", log.Fields{"group": group})
91 if group == nil {
92 logger.Error(ctx, "unable-to-delete-group--invalid-argument--group-is-nil")
93 return olterrors.NewErrInvalidValue(log.Fields{"group": group}, nil)
94 }
95 groupToOlt := openoltpb2.Group{
96 GroupId: group.Desc.GroupId,
97 }
98 logger.Debugw(ctx, "deleting-group-from-device", log.Fields{"groupToOlt": groupToOlt})
bseenivaa1622112025-12-11 18:24:02 +053099 subCtx, cancel := context.WithTimeout(log.WithSpanFromContext(context.Background(), ctx), g.deviceHandler.cfg.RPCTimeout)
100 _, err := g.deviceHandler.Client.DeleteGroup(subCtx, &groupToOlt)
101 cancel()
Girish Gowdra9602eb42020-09-09 15:50:39 -0700102 if err != nil {
103 logger.Errorw(ctx, "delete-group-failed-on-dev", log.Fields{"groupToOlt": groupToOlt, "err": err})
104 return olterrors.NewErrAdapter("delete-group-operation-failed", log.Fields{"groupToOlt": groupToOlt}, err)
105 }
Akash Kankanala041a2122024-10-16 15:49:22 +0530106 // remove group from the store
Girish Gowdra9602eb42020-09-09 15:50:39 -0700107 if err := g.resourceMgr.RemoveFlowGroupFromKVStore(ctx, group.Desc.GroupId, false); err != nil {
Girish Gowdraa09aeab2020-09-14 16:30:52 -0700108 return olterrors.NewErrPersistence("delete", "flow-group", uint64(group.Desc.GroupId), log.Fields{"group": group}, err)
Girish Gowdra9602eb42020-09-09 15:50:39 -0700109 }
110 logger.Debugw(ctx, "delete-group-operation-performed-on-the-device-successfully ", log.Fields{"groupToOlt": groupToOlt})
111 return nil
112}
113
114// ModifyGroup updates the group
115func (g *OpenOltGroupMgr) ModifyGroup(ctx context.Context, group *ofp.OfpGroupEntry) error {
116 logger.Infow(ctx, "modify-group", log.Fields{"group": group})
117 if group == nil || group.Desc == nil {
118 return olterrors.NewErrInvalidValue(log.Fields{"group": group}, nil)
119 }
120 newGroup := g.buildGroup(ctx, group.Desc.GroupId, group.Desc.Buckets)
Akash Kankanala041a2122024-10-16 15:49:22 +0530121 // get existing members of the group
Girish Gowdra9602eb42020-09-09 15:50:39 -0700122 val, groupExists, err := g.getFlowGroupFromKVStore(ctx, group.Desc.GroupId, false)
123 if err != nil {
124 return olterrors.NewErrNotFound("flow-group-in-kv-store", log.Fields{"groupId": group.Desc.GroupId}, err)
125 }
126 var current *openoltpb2.Group // represents the group on the device
127 if groupExists {
128 // group already exists
129 current = g.buildGroup(ctx, group.Desc.GroupId, val.Desc.GetBuckets())
130 logger.Debugw(ctx, "modify-group--group exists",
131 log.Fields{
132 "group on the device": val,
133 "new": group})
134 } else {
135 current = g.buildGroup(ctx, group.Desc.GroupId, nil)
136 }
137 logger.Debugw(ctx, "modify-group--comparing-current-and-new",
138 log.Fields{
139 "group on the device": current,
140 "new": newGroup})
141 // get members to be added
142 membersToBeAdded := g.findDiff(current, newGroup)
143 // get members to be removed
144 membersToBeRemoved := g.findDiff(newGroup, current)
145 logger.Infow(ctx, "modify-group--differences found", log.Fields{
146 "membersToBeAdded": membersToBeAdded,
147 "membersToBeRemoved": membersToBeRemoved,
148 "groupId": group.Desc.GroupId})
149 groupToOlt := openoltpb2.Group{
150 GroupId: group.Desc.GroupId,
151 }
152 var errAdd, errRemoved error
153 if len(membersToBeAdded) > 0 {
154 groupToOlt.Command = openoltpb2.Group_ADD_MEMBERS
155 groupToOlt.Members = membersToBeAdded
Akash Kankanala041a2122024-10-16 15:49:22 +0530156 // execute addMembers
Girish Gowdra9602eb42020-09-09 15:50:39 -0700157 errAdd = g.callGroupAddRemove(ctx, &groupToOlt)
158 }
159 if len(membersToBeRemoved) > 0 {
160 groupToOlt.Command = openoltpb2.Group_REMOVE_MEMBERS
161 groupToOlt.Members = membersToBeRemoved
Akash Kankanala041a2122024-10-16 15:49:22 +0530162 // execute removeMembers
Girish Gowdra9602eb42020-09-09 15:50:39 -0700163 errRemoved = g.callGroupAddRemove(ctx, &groupToOlt)
164 }
Akash Kankanala041a2122024-10-16 15:49:22 +0530165 // save the modified group
Girish Gowdra9602eb42020-09-09 15:50:39 -0700166 if errAdd == nil && errRemoved == nil {
167 if err := g.resourceMgr.AddFlowGroupToKVStore(ctx, group, false); err != nil {
Girish Gowdraa09aeab2020-09-14 16:30:52 -0700168 return olterrors.NewErrPersistence("add", "flow-group", uint64(group.Desc.GroupId), log.Fields{"group": group}, err)
Girish Gowdra9602eb42020-09-09 15:50:39 -0700169 }
170 logger.Infow(ctx, "modify-group-was-success--storing-group",
171 log.Fields{
172 "group": group,
173 "existingGroup": current})
174 } else {
175 logger.Warnw(ctx, "one-of-the-group-add/remove-operations-failed--cannot-save-group-modifications",
176 log.Fields{"group": group})
177 if errAdd != nil {
178 return errAdd
179 }
180 return errRemoved
181 }
182 return nil
183}
184
Joey Armstrong3f0e2422023-07-05 18:25:41 -0400185// LoadInterfaceToMulticastQueueMap reads multicast queues per interface from the KV store
186// and put them into interfaceToMcastQueueMap.
Girish Gowdra9602eb42020-09-09 15:50:39 -0700187func (g *OpenOltGroupMgr) LoadInterfaceToMulticastQueueMap(ctx context.Context) {
188 storedMulticastQueueMap, err := g.resourceMgr.GetMcastQueuePerInterfaceMap(ctx)
189 if err != nil {
190 logger.Error(ctx, "failed-to-get-pon-interface-to-multicast-queue-map")
191 return
192 }
193 for intf, queueInfo := range storedMulticastQueueMap {
194 q := QueueInfoBrief{
195 gemPortID: queueInfo[0],
196 servicePriority: queueInfo[1],
197 }
198 g.interfaceToMcastQueueMap[intf] = &q
199 }
200}
201
Joey Armstrong3f0e2422023-07-05 18:25:41 -0400202// GetInterfaceToMcastQueueMap gets the mcast queue mapped to to the PON interface
Girish Gowdra9602eb42020-09-09 15:50:39 -0700203func (g *OpenOltGroupMgr) GetInterfaceToMcastQueueMap(intfID uint32) (*QueueInfoBrief, bool) {
204 g.interfaceToMcastQueueMapLock.RLock()
205 defer g.interfaceToMcastQueueMapLock.RUnlock()
206 val, present := g.interfaceToMcastQueueMap[intfID]
207 return val, present
208}
209
Joey Armstrong3f0e2422023-07-05 18:25:41 -0400210// UpdateInterfaceToMcastQueueMap updates the mcast queue information mapped to a given PON interface
Girish Gowdra9602eb42020-09-09 15:50:39 -0700211func (g *OpenOltGroupMgr) UpdateInterfaceToMcastQueueMap(intfID uint32, val *QueueInfoBrief) {
212 g.interfaceToMcastQueueMapLock.Lock()
213 defer g.interfaceToMcastQueueMapLock.Unlock()
214 g.interfaceToMcastQueueMap[intfID] = val
215}
216
Joey Armstrong3f0e2422023-07-05 18:25:41 -0400217// //////////////////////////////////////////////
218//
219// INTERNAL or UNEXPORTED FUNCTIONS //
220//
221// //////////////////////////////////////////////
222// getFlowGroupFromKVStore fetches and returns flow group from the KV store. Returns (nil, false, error) if any problem occurs during
223// fetching the data. Returns (group, true, nil) if the group is fetched and returned successfully.
224// Returns (nil, false, nil) if the group does not exists in the KV store.
Girish Gowdra9602eb42020-09-09 15:50:39 -0700225func (g *OpenOltGroupMgr) getFlowGroupFromKVStore(ctx context.Context, groupID uint32, cached bool) (*ofp.OfpGroupEntry, bool, error) {
226 exists, groupInfo, err := g.resourceMgr.GetFlowGroupFromKVStore(ctx, groupID, cached)
227 if err != nil {
228 return nil, false, olterrors.NewErrNotFound("flow-group", log.Fields{"group-id": groupID}, err)
229 }
230 if exists {
231 return newGroup(groupInfo.GroupID, groupInfo.OutPorts), exists, nil
232 }
233 return nil, exists, nil
234}
235func newGroup(groupID uint32, outPorts []uint32) *ofp.OfpGroupEntry {
236 groupDesc := ofp.OfpGroupDesc{
237 Type: ofp.OfpGroupType_OFPGT_ALL,
238 GroupId: groupID,
239 }
240 groupEntry := ofp.OfpGroupEntry{
241 Desc: &groupDesc,
242 }
243 for i := 0; i < len(outPorts); i++ {
244 var acts []*ofp.OfpAction
245 acts = append(acts, flows.Output(outPorts[i]))
246 bucket := ofp.OfpBucket{
247 Actions: acts,
248 }
249 groupDesc.Buckets = append(groupDesc.Buckets, &bucket)
250 }
251 return &groupEntry
252}
253
Joey Armstrong3f0e2422023-07-05 18:25:41 -0400254// buildGroupAction creates and returns a group action
Girish Gowdra9602eb42020-09-09 15:50:39 -0700255func (g *OpenOltGroupMgr) buildGroupAction() *openoltpb2.Action {
256 var actionCmd openoltpb2.ActionCmd
257 var action openoltpb2.Action
258 action.Cmd = &actionCmd
Akash Kankanala041a2122024-10-16 15:49:22 +0530259 // pop outer vlan
Girish Gowdra9602eb42020-09-09 15:50:39 -0700260 action.Cmd.RemoveOuterTag = true
261 return &action
262}
263
Joey Armstrong3f0e2422023-07-05 18:25:41 -0400264// callGroupAddRemove performs add/remove buckets operation for the indicated group
Girish Gowdra9602eb42020-09-09 15:50:39 -0700265func (g *OpenOltGroupMgr) callGroupAddRemove(ctx context.Context, group *openoltpb2.Group) error {
266 if err := g.performGroupOperation(ctx, group); err != nil {
267 st, _ := status.FromError(err)
Akash Kankanala041a2122024-10-16 15:49:22 +0530268 // ignore already exists error code
Girish Gowdra9602eb42020-09-09 15:50:39 -0700269 if st.Code() != codes.AlreadyExists {
270 return olterrors.NewErrGroupOp("groupAddRemove", group.GroupId, log.Fields{"status": st}, err)
271 }
272 }
273 return nil
274}
275
Joey Armstrong3f0e2422023-07-05 18:25:41 -0400276// findDiff compares group members and finds members which only exists in groups2
Girish Gowdra9602eb42020-09-09 15:50:39 -0700277func (g *OpenOltGroupMgr) findDiff(group1 *openoltpb2.Group, group2 *openoltpb2.Group) []*openoltpb2.GroupMember {
278 var members []*openoltpb2.GroupMember
279 for _, bucket := range group2.Members {
280 if !g.contains(group1.Members, bucket) {
281 // bucket does not exist and must be added
282 members = append(members, bucket)
283 }
284 }
285 return members
286}
287
Joey Armstrong3f0e2422023-07-05 18:25:41 -0400288// contains returns true if the members list contains the given member; false otherwise
Girish Gowdra9602eb42020-09-09 15:50:39 -0700289func (g *OpenOltGroupMgr) contains(members []*openoltpb2.GroupMember, member *openoltpb2.GroupMember) bool {
290 for _, groupMember := range members {
291 if groupMember.InterfaceId == member.InterfaceId {
292 return true
293 }
294 }
295 return false
296}
297
Joey Armstrong3f0e2422023-07-05 18:25:41 -0400298// performGroupOperation call performGroupOperation operation of openolt proto
Girish Gowdra9602eb42020-09-09 15:50:39 -0700299func (g *OpenOltGroupMgr) performGroupOperation(ctx context.Context, group *openoltpb2.Group) error {
300 logger.Debugw(ctx, "sending-group-to-device",
301 log.Fields{
302 "groupToOlt": group,
303 "command": group.Command})
bseenivaa1622112025-12-11 18:24:02 +0530304 subCtx, cancel := context.WithTimeout(log.WithSpanFromContext(context.Background(), ctx), g.deviceHandler.cfg.RPCTimeout)
305 _, err := g.deviceHandler.Client.PerformGroupOperation(subCtx, group)
306 cancel()
Girish Gowdra9602eb42020-09-09 15:50:39 -0700307 if err != nil {
308 return olterrors.NewErrAdapter("group-operation-failed", log.Fields{"groupToOlt": group}, err)
309 }
310 return nil
311}
312
Joey Armstrong3f0e2422023-07-05 18:25:41 -0400313// buildGroup build openoltpb2.Group from given group id and bucket list
Girish Gowdra9602eb42020-09-09 15:50:39 -0700314func (g *OpenOltGroupMgr) buildGroup(ctx context.Context, groupID uint32, buckets []*ofp.OfpBucket) *openoltpb2.Group {
315 group := openoltpb2.Group{
316 GroupId: groupID}
317 // create members of the group
318 for _, ofBucket := range buckets {
319 member := g.buildMember(ctx, ofBucket)
320 if member != nil && !g.contains(group.Members, member) {
321 group.Members = append(group.Members, member)
322 }
323 }
324 return &group
325}
326
Joey Armstrong3f0e2422023-07-05 18:25:41 -0400327// buildMember builds openoltpb2.GroupMember from an OpenFlow bucket
Girish Gowdra9602eb42020-09-09 15:50:39 -0700328func (g *OpenOltGroupMgr) buildMember(ctx context.Context, ofBucket *ofp.OfpBucket) *openoltpb2.GroupMember {
329 var outPort uint32
330 outPortFound := false
331 for _, ofAction := range ofBucket.Actions {
332 if ofAction.Type == ofp.OfpActionType_OFPAT_OUTPUT {
333 outPort = ofAction.GetOutput().Port
334 outPortFound = true
335 }
336 }
337 if !outPortFound {
338 logger.Debugw(ctx, "bucket-skipped-since-no-out-port-found-in-it", log.Fields{"ofBucket": ofBucket})
339 return nil
340 }
Mahir Gunyel85f61c12021-10-06 11:53:45 -0700341 interfaceID := plt.IntfIDFromUniPortNum(outPort)
Girish Gowdra9602eb42020-09-09 15:50:39 -0700342 logger.Debugw(ctx, "got-associated-interface-id-of-the-port",
343 log.Fields{
344 "portNumber:": outPort,
345 "interfaceId:": interfaceID})
346 g.interfaceToMcastQueueMapLock.RLock()
347 defer g.interfaceToMcastQueueMapLock.RUnlock()
348 if groupInfo, ok := g.interfaceToMcastQueueMap[interfaceID]; ok {
349 member := openoltpb2.GroupMember{
350 InterfaceId: interfaceID,
351 InterfaceType: openoltpb2.GroupMember_PON,
352 GemPortId: groupInfo.gemPortID,
353 Priority: groupInfo.servicePriority,
354 }
Akash Kankanala041a2122024-10-16 15:49:22 +0530355 // add member to the group
Girish Gowdra9602eb42020-09-09 15:50:39 -0700356 return &member
357 }
358 logger.Warnf(ctx, "bucket-skipped-since-interface-2-gem-mapping-cannot-be-found", log.Fields{"ofBucket": ofBucket})
359 return nil
360}