khenaidoo | b920354 | 2018-09-17 22:56:37 -0400 | [diff] [blame] | 1 | /* |
Joey Armstrong | 7a9af44 | 2024-01-03 19:26:36 -0500 | [diff] [blame] | 2 | * Copyright 2018-2024 Open Networking Foundation (ONF) and the ONF Contributors |
khenaidoo | b920354 | 2018-09-17 22:56:37 -0400 | [diff] [blame] | 3 | |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
npujar | 1d86a52 | 2019-11-14 17:11:16 +0530 | [diff] [blame] | 16 | |
Kent Hagerman | 2b21604 | 2020-04-03 18:28:56 -0400 | [diff] [blame] | 17 | package device |
khenaidoo | b920354 | 2018-09-17 22:56:37 -0400 | [diff] [blame] | 18 | |
| 19 | import ( |
| 20 | "context" |
Matteo Scandolo | 360605d | 2019-11-05 18:29:17 -0800 | [diff] [blame] | 21 | "encoding/hex" |
David Bainbridge | d1afd66 | 2020-03-26 18:27:41 -0700 | [diff] [blame] | 22 | "sync" |
| 23 | "time" |
| 24 | |
khenaidoo | b920354 | 2018-09-17 22:56:37 -0400 | [diff] [blame] | 25 | "github.com/gogo/protobuf/proto" |
sbarbari | 17d7e22 | 2019-11-05 10:02:29 -0500 | [diff] [blame] | 26 | "github.com/opencord/voltha-go/db/model" |
Kent Hagerman | 433a31a | 2020-05-20 19:04:48 -0400 | [diff] [blame] | 27 | "github.com/opencord/voltha-go/rw_core/core/device/flow" |
| 28 | "github.com/opencord/voltha-go/rw_core/core/device/group" |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 29 | lp "github.com/opencord/voltha-go/rw_core/core/device/logical_port" |
Kent Hagerman | 433a31a | 2020-05-20 19:04:48 -0400 | [diff] [blame] | 30 | "github.com/opencord/voltha-go/rw_core/core/device/meter" |
npujar | 1d86a52 | 2019-11-14 17:11:16 +0530 | [diff] [blame] | 31 | fd "github.com/opencord/voltha-go/rw_core/flowdecomposition" |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 32 | "github.com/opencord/voltha-go/rw_core/route" |
Scott Baker | b671a86 | 2019-10-24 10:53:40 -0700 | [diff] [blame] | 33 | coreutils "github.com/opencord/voltha-go/rw_core/utils" |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 34 | fu "github.com/opencord/voltha-lib-go/v7/pkg/flows" |
| 35 | "github.com/opencord/voltha-lib-go/v7/pkg/log" |
khenaidoo | 9beaaf1 | 2021-10-19 17:32:01 -0400 | [diff] [blame] | 36 | ca "github.com/opencord/voltha-protos/v5/go/core_adapter" |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 37 | ofp "github.com/opencord/voltha-protos/v5/go/openflow_13" |
| 38 | "github.com/opencord/voltha-protos/v5/go/voltha" |
khenaidoo | b920354 | 2018-09-17 22:56:37 -0400 | [diff] [blame] | 39 | "google.golang.org/grpc/codes" |
| 40 | "google.golang.org/grpc/status" |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 41 | ) |
| 42 | |
Kent Hagerman | 2b21604 | 2020-04-03 18:28:56 -0400 | [diff] [blame] | 43 | // LogicalAgent represent attributes of logical device agent |
Akash Reddy Kankanala | 929cc00 | 2025-04-08 15:05:21 +0530 | [diff] [blame] | 44 | // |
| 45 | //nolint:govet |
Kent Hagerman | 2b21604 | 2020-04-03 18:28:56 -0400 | [diff] [blame] | 46 | type LogicalAgent struct { |
Akash Reddy Kankanala | 929cc00 | 2025-04-08 15:05:21 +0530 | [diff] [blame] | 47 | orderedEvents orderedEvents |
| 48 | deviceMgr *Manager |
| 49 | ldeviceMgr *LogicalManager |
| 50 | ldProxy *model.Proxy |
| 51 | deviceRoutes *route.DeviceRoutes |
| 52 | flowDecomposer *fd.FlowDecomposer |
| 53 | logicalDevice *voltha.LogicalDevice |
| 54 | requestQueue *coreutils.RequestQueue |
| 55 | |
| 56 | flowCache *flow.Cache |
| 57 | meterLoader *meter.Loader |
| 58 | groupCache *group.Cache |
| 59 | portLoader *lp.Loader |
Kent Hagerman | fa9d6d4 | 2020-05-25 11:49:40 -0400 | [diff] [blame] | 60 | logicalDeviceID string |
| 61 | serialNumber string |
| 62 | rootDeviceID string |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 63 | internalTimeout time.Duration |
Kent Hagerman | fa9d6d4 | 2020-05-25 11:49:40 -0400 | [diff] [blame] | 64 | startOnce sync.Once |
| 65 | stopOnce sync.Once |
Sridhar Ravindra | 7cb6eda | 2024-12-06 07:53:51 +0530 | [diff] [blame] | 66 | exitChannel chan int |
Mahir Gunyel | addb66a | 2020-04-29 18:08:50 -0700 | [diff] [blame] | 67 | |
Akash Reddy Kankanala | 929cc00 | 2025-04-08 15:05:21 +0530 | [diff] [blame] | 68 | stopped bool |
Mahir Gunyel | addb66a | 2020-04-29 18:08:50 -0700 | [diff] [blame] | 69 | } |
| 70 | |
Akash Reddy Kankanala | 929cc00 | 2025-04-08 15:05:21 +0530 | [diff] [blame] | 71 | func newLogicalAgent(id string, sn string, deviceID string, ldeviceMgr *LogicalManager, |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 72 | deviceMgr *Manager, dbProxy *model.Path, ldProxy *model.Proxy, internalTimeout time.Duration) *LogicalAgent { |
Kent Hagerman | 2a07b86 | 2020-06-19 15:23:07 -0400 | [diff] [blame] | 73 | return &LogicalAgent{ |
Kent Hagerman | f5a6735 | 2020-04-30 15:15:26 -0400 | [diff] [blame] | 74 | logicalDeviceID: id, |
| 75 | serialNumber: sn, |
| 76 | rootDeviceID: deviceID, |
| 77 | deviceMgr: deviceMgr, |
| 78 | ldProxy: ldProxy, |
| 79 | ldeviceMgr: ldeviceMgr, |
Kent Hagerman | 2a07b86 | 2020-06-19 15:23:07 -0400 | [diff] [blame] | 80 | deviceRoutes: route.NewDeviceRoutes(id, deviceID, deviceMgr.listDevicePorts), |
Kent Hagerman | 6031aad | 2020-07-29 16:36:33 -0400 | [diff] [blame] | 81 | flowDecomposer: fd.NewFlowDecomposer(deviceMgr.getDeviceReadOnly), |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 82 | internalTimeout: internalTimeout, |
Kent Hagerman | f5a6735 | 2020-04-30 15:15:26 -0400 | [diff] [blame] | 83 | requestQueue: coreutils.NewRequestQueue(), |
Sridhar Ravindra | 7cb6eda | 2024-12-06 07:53:51 +0530 | [diff] [blame] | 84 | exitChannel: make(chan int, 1), |
Kent Hagerman | 433a31a | 2020-05-20 19:04:48 -0400 | [diff] [blame] | 85 | |
khenaidoo | 1a0d622 | 2021-06-30 16:48:44 -0400 | [diff] [blame] | 86 | flowCache: flow.NewCache(), |
| 87 | groupCache: group.NewCache(), |
| 88 | meterLoader: meter.NewLoader(dbProxy.SubPath("logical_meters").Proxy(id)), |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 89 | portLoader: lp.NewLoader(dbProxy.SubPath("logical_ports").Proxy(id)), |
Kent Hagerman | 433a31a | 2020-05-20 19:04:48 -0400 | [diff] [blame] | 90 | } |
khenaidoo | b920354 | 2018-09-17 22:56:37 -0400 | [diff] [blame] | 91 | } |
| 92 | |
khenaidoo | 4d4802d | 2018-10-04 21:59:49 -0400 | [diff] [blame] | 93 | // start creates the logical device and add it to the data model |
Sridhar Ravindra | 7cb6eda | 2024-12-06 07:53:51 +0530 | [diff] [blame] | 94 | func (agent *LogicalAgent) start(ctx context.Context, logicalDeviceExist bool, logicalDevice *voltha.LogicalDevice) { |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 95 | needToStart := false |
| 96 | if agent.startOnce.Do(func() { needToStart = true }); !needToStart { |
Sridhar Ravindra | 7cb6eda | 2024-12-06 07:53:51 +0530 | [diff] [blame] | 97 | logger.Debug(ctx, "starting-logical-device-agent already running") |
| 98 | return |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 99 | } |
| 100 | |
khenaidoo | 7585a96 | 2021-06-10 16:15:38 -0400 | [diff] [blame] | 101 | logger.Infow(ctx, "starting-logical-device-agent", log.Fields{"logical-device-id": agent.logicalDeviceID, "load-from-db": logicalDeviceExist}) |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 102 | |
| 103 | var startSucceeded bool |
| 104 | defer func() { |
| 105 | if !startSucceeded { |
Sridhar Ravindra | 7cb6eda | 2024-12-06 07:53:51 +0530 | [diff] [blame] | 106 | if stopErr := agent.stop(ctx); stopErr != nil { |
| 107 | logger.Errorw(ctx, "failed-to-cleanup-after-unsuccessful-start", log.Fields{"logical-device-id": agent.logicalDeviceID, "error": stopErr}) |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 108 | } |
| 109 | } |
| 110 | }() |
| 111 | |
khenaidoo | 297cd25 | 2019-02-07 22:10:23 -0500 | [diff] [blame] | 112 | var ld *voltha.LogicalDevice |
khenaidoo | 7585a96 | 2021-06-10 16:15:38 -0400 | [diff] [blame] | 113 | if !logicalDeviceExist { |
Akash Reddy Kankanala | 929cc00 | 2025-04-08 15:05:21 +0530 | [diff] [blame] | 114 | // Build the logical device based on information retrieved from the device adapter |
khenaidoo | 9beaaf1 | 2021-10-19 17:32:01 -0400 | [diff] [blame] | 115 | var switchCap *ca.SwitchCapability |
khenaidoo | 297cd25 | 2019-02-07 22:10:23 -0500 | [diff] [blame] | 116 | var err error |
Sridhar Ravindra | 7cb6eda | 2024-12-06 07:53:51 +0530 | [diff] [blame] | 117 | |
npujar | 1d86a52 | 2019-11-14 17:11:16 +0530 | [diff] [blame] | 118 | if switchCap, err = agent.deviceMgr.getSwitchCapability(ctx, agent.rootDeviceID); err != nil { |
Sridhar Ravindra | 7cb6eda | 2024-12-06 07:53:51 +0530 | [diff] [blame] | 119 | logger.Warnw(ctx, "failed-to-get-switch-capability", log.Fields{"root-device-id": agent.rootDeviceID, "error": err}) |
| 120 | switchCapTicker := time.NewTicker(time.Second * 2) |
| 121 | defer switchCapTicker.Stop() |
| 122 | |
| 123 | // Start a retry loop to get switch capability of the OLT device from adapter |
| 124 | for { |
| 125 | select { |
| 126 | case <-switchCapTicker.C: |
| 127 | if switchCap, err = agent.deviceMgr.getSwitchCapability(ctx, agent.rootDeviceID); err == nil { |
| 128 | logger.Infow(ctx, "received switch capability, proceeding to start logical device agent", log.Fields{"root-device-id": agent.rootDeviceID}) |
| 129 | } |
| 130 | // Before retrying, check if the agent has stopped |
| 131 | case _, ok := (<-agent.exitChannel): |
| 132 | if !ok { |
| 133 | logger.Warnw(ctx, "agent stopped, exit retrying get-switch-capability", log.Fields{"root-device-id": agent.rootDeviceID}) |
| 134 | return |
| 135 | } |
| 136 | } |
| 137 | // Break the for loop as we have received the switch capability from adapter |
| 138 | if err == nil { |
| 139 | break |
| 140 | } |
| 141 | logger.Warnw(ctx, "retrying get-switch-capability", log.Fields{"root-device-id": agent.rootDeviceID, "error": err}) |
| 142 | } |
khenaidoo | 7e3d8f1 | 2019-08-02 16:06:30 -0400 | [diff] [blame] | 143 | } |
npujar | 1d86a52 | 2019-11-14 17:11:16 +0530 | [diff] [blame] | 144 | ld = &voltha.LogicalDevice{Id: agent.logicalDeviceID, RootDeviceId: agent.rootDeviceID} |
khenaidoo | 297cd25 | 2019-02-07 22:10:23 -0500 | [diff] [blame] | 145 | |
| 146 | // Create the datapath ID (uint64) using the logical device ID (based on the MAC Address) |
| 147 | var datapathID uint64 |
Kent Hagerman | 2b21604 | 2020-04-03 18:28:56 -0400 | [diff] [blame] | 148 | if datapathID, err = coreutils.CreateDataPathID(agent.serialNumber); err != nil { |
Sridhar Ravindra | 7cb6eda | 2024-12-06 07:53:51 +0530 | [diff] [blame] | 149 | logger.Errorw(ctx, "failed-to-create-datapath-id", log.Fields{"serial-number": agent.serialNumber, "error": err}) |
| 150 | return |
khenaidoo | 297cd25 | 2019-02-07 22:10:23 -0500 | [diff] [blame] | 151 | } |
| 152 | ld.DatapathId = datapathID |
khenaidoo | 7e3d8f1 | 2019-08-02 16:06:30 -0400 | [diff] [blame] | 153 | ld.Desc = (proto.Clone(switchCap.Desc)).(*ofp.OfpDesc) |
Rohan Agrawal | 31f2180 | 2020-06-12 05:38:46 +0000 | [diff] [blame] | 154 | logger.Debugw(ctx, "Switch-capability", log.Fields{"Desc": ld.Desc, "fromAd": switchCap.Desc}) |
khenaidoo | 7e3d8f1 | 2019-08-02 16:06:30 -0400 | [diff] [blame] | 155 | ld.SwitchFeatures = (proto.Clone(switchCap.SwitchFeatures)).(*ofp.OfpSwitchFeatures) |
khenaidoo | 297cd25 | 2019-02-07 22:10:23 -0500 | [diff] [blame] | 156 | |
khenaidoo | 297cd25 | 2019-02-07 22:10:23 -0500 | [diff] [blame] | 157 | // Save the logical device |
Akash Reddy Kankanala | 929cc00 | 2025-04-08 15:05:21 +0530 | [diff] [blame] | 158 | if err = agent.ldProxy.Set(ctx, ld.Id, ld); err != nil { |
Rohan Agrawal | 31f2180 | 2020-06-12 05:38:46 +0000 | [diff] [blame] | 159 | logger.Errorw(ctx, "failed-to-add-logical-device", log.Fields{"logical-device-id": agent.logicalDeviceID}) |
Sridhar Ravindra | 7cb6eda | 2024-12-06 07:53:51 +0530 | [diff] [blame] | 160 | return |
Thomas Lee S | e5a4401 | 2019-11-07 20:32:24 +0530 | [diff] [blame] | 161 | } |
Himani Chawla | b4c2591 | 2020-11-12 17:16:38 +0530 | [diff] [blame] | 162 | logger.Debugw(ctx, "logical-device-created", log.Fields{"logical-device-id": agent.logicalDeviceID, "root-id": ld.RootDeviceId}) |
khenaidoo | 6e55d9e | 2019-12-12 18:26:26 -0500 | [diff] [blame] | 163 | |
Kent Hagerman | fa9d6d4 | 2020-05-25 11:49:40 -0400 | [diff] [blame] | 164 | agent.logicalDevice = ld |
khenaidoo | fc1314d | 2019-03-14 09:34:21 -0400 | [diff] [blame] | 165 | |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 166 | // Setup the logicalports - internal processing, no need to propagate the client context |
nikesh.krishnan | 95142d5 | 2023-02-24 15:32:11 +0530 | [diff] [blame] | 167 | |
| 168 | err = agent.setupLogicalPorts(ctx) |
| 169 | if err != nil { |
| 170 | logger.Errorw(ctx, "unable-to-setup-logical-ports", log.Fields{"error": err}) |
| 171 | } |
| 172 | |
khenaidoo | 297cd25 | 2019-02-07 22:10:23 -0500 | [diff] [blame] | 173 | } else { |
khenaidoo | 7585a96 | 2021-06-10 16:15:38 -0400 | [diff] [blame] | 174 | // Check to see if we need to load from dB |
| 175 | ld = logicalDevice |
| 176 | if logicalDevice == nil { |
| 177 | // load from dB |
| 178 | ld = &voltha.LogicalDevice{} |
| 179 | have, err := agent.ldProxy.Get(ctx, agent.logicalDeviceID, ld) |
| 180 | if err != nil { |
Sridhar Ravindra | 7cb6eda | 2024-12-06 07:53:51 +0530 | [diff] [blame] | 181 | logger.Errorw(ctx, "failed-to-load-logical-device-from-db", log.Fields{"logical-device-id": agent.logicalDeviceID, "error": err}) |
| 182 | return |
khenaidoo | 7585a96 | 2021-06-10 16:15:38 -0400 | [diff] [blame] | 183 | } else if !have { |
Sridhar Ravindra | 7cb6eda | 2024-12-06 07:53:51 +0530 | [diff] [blame] | 184 | err := status.Errorf(codes.NotFound, "logical_device-%s", agent.logicalDeviceID) |
| 185 | logger.Errorw(ctx, "logical-device-not-found-in-db", log.Fields{"logical-device-id": agent.logicalDeviceID, "error": err}) |
| 186 | return |
khenaidoo | 7585a96 | 2021-06-10 16:15:38 -0400 | [diff] [blame] | 187 | } |
khenaidoo | 297cd25 | 2019-02-07 22:10:23 -0500 | [diff] [blame] | 188 | } |
Kent Hagerman | 4f355f5 | 2020-03-30 16:01:33 -0400 | [diff] [blame] | 189 | |
khenaidoo | 8c3303d | 2019-02-13 14:59:39 -0500 | [diff] [blame] | 190 | // Update the root device Id |
npujar | 1d86a52 | 2019-11-14 17:11:16 +0530 | [diff] [blame] | 191 | agent.rootDeviceID = ld.RootDeviceId |
khenaidoo | 3d3b8c2 | 2019-05-22 18:10:39 -0400 | [diff] [blame] | 192 | |
khenaidoo | 6e55d9e | 2019-12-12 18:26:26 -0500 | [diff] [blame] | 193 | // Update the last data |
Kent Hagerman | fa9d6d4 | 2020-05-25 11:49:40 -0400 | [diff] [blame] | 194 | agent.logicalDevice = ld |
khenaidoo | 6e55d9e | 2019-12-12 18:26:26 -0500 | [diff] [blame] | 195 | |
Kent Hagerman | 2a07b86 | 2020-06-19 15:23:07 -0400 | [diff] [blame] | 196 | // now that the root device is known, create DeviceRoutes with it |
| 197 | agent.deviceRoutes = route.NewDeviceRoutes(agent.logicalDeviceID, agent.rootDeviceID, agent.deviceMgr.listDevicePorts) |
| 198 | |
khenaidoo | 1a0d622 | 2021-06-30 16:48:44 -0400 | [diff] [blame] | 199 | // load the meters from KV to cache |
| 200 | agent.meterLoader.Load(ctx) |
| 201 | |
khenaidoo | 7585a96 | 2021-06-10 16:15:38 -0400 | [diff] [blame] | 202 | // load the logical ports from KV to cache |
Kent Hagerman | fa9d6d4 | 2020-05-25 11:49:40 -0400 | [diff] [blame] | 203 | agent.portLoader.Load(ctx) |
khenaidoo | b920354 | 2018-09-17 22:56:37 -0400 | [diff] [blame] | 204 | } |
khenaidoo | fc1314d | 2019-03-14 09:34:21 -0400 | [diff] [blame] | 205 | |
khenaidoo | 820197c | 2020-02-13 16:35:33 -0500 | [diff] [blame] | 206 | // Setup the device routes. Building routes may fail if the pre-conditions are not satisfied (e.g. no PON ports present) |
khenaidoo | 7585a96 | 2021-06-10 16:15:38 -0400 | [diff] [blame] | 207 | if logicalDeviceExist { |
nikesh.krishnan | 95142d5 | 2023-02-24 15:32:11 +0530 | [diff] [blame] | 208 | |
| 209 | if err := agent.buildRoutes(ctx); err != nil { |
| 210 | logger.Warn(ctx, "routes-not-ready", log.Fields{"logical-device-id": agent.logicalDeviceID, "error": err}) |
| 211 | } |
| 212 | |
khenaidoo | 4c9e559 | 2019-09-09 16:20:41 -0400 | [diff] [blame] | 213 | } |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 214 | startSucceeded = true |
Sridhar Ravindra | 7cb6eda | 2024-12-06 07:53:51 +0530 | [diff] [blame] | 215 | agent.ldeviceMgr.addLogicalDeviceAgentToMap(agent) |
khenaidoo | b920354 | 2018-09-17 22:56:37 -0400 | [diff] [blame] | 216 | } |
| 217 | |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 218 | // stop stops the logical device agent. This removes the logical device from the data model. |
Kent Hagerman | 2b21604 | 2020-04-03 18:28:56 -0400 | [diff] [blame] | 219 | func (agent *LogicalAgent) stop(ctx context.Context) error { |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 220 | var returnErr error |
| 221 | agent.stopOnce.Do(func() { |
Himani Chawla | b4c2591 | 2020-11-12 17:16:38 +0530 | [diff] [blame] | 222 | logger.Info(ctx, "stopping-logical-device-agent") |
khenaidoo | 8c3303d | 2019-02-13 14:59:39 -0500 | [diff] [blame] | 223 | |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 224 | if err := agent.requestQueue.WaitForGreenLight(ctx); err != nil { |
| 225 | // This should never happen - an error is returned only if the agent is stopped and an agent is only stopped once. |
| 226 | returnErr = err |
| 227 | return |
| 228 | } |
| 229 | defer agent.requestQueue.RequestComplete() |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 230 | subCtx, cancel := context.WithTimeout(log.WithSpanFromContext(context.Background(), ctx), agent.internalTimeout) |
Himani Chawla | 40af270 | 2021-01-27 15:06:30 +0530 | [diff] [blame] | 231 | // Before deletion of the logical agent, make sure all events for ldagent are sent to avoid race conditions |
| 232 | if err := agent.orderedEvents.waitForAllEventsToBeSent(subCtx, cancel); err != nil { |
Akash Reddy Kankanala | 929cc00 | 2025-04-08 15:05:21 +0530 | [diff] [blame] | 233 | // Log the error here |
Himani Chawla | 40af270 | 2021-01-27 15:06:30 +0530 | [diff] [blame] | 234 | logger.Errorw(ctx, "failed-to-send-all-events-on-the-logical-device-before-deletion", |
| 235 | log.Fields{"error": err, "logical-device-id": agent.logicalDeviceID}) |
| 236 | } |
Girish Gowdra | 3d922b6 | 2021-12-06 15:27:02 +0530 | [diff] [blame] | 237 | |
Girish Gowdra | 06a0ce2 | 2021-12-14 11:09:10 +0530 | [diff] [blame] | 238 | if err := agent.ldeviceMgr.deleteAllLogicalMetersForLogicalDevice(ctx, agent.logicalDeviceID); err != nil { |
| 239 | // Just log the error. The logical device or port may already have been deleted before this callback is invoked. |
| 240 | logger.Warnw(ctx, "delete-logical-meters-error", log.Fields{"device-id": agent.logicalDeviceID, "error": err}) |
| 241 | } |
| 242 | |
Akash Reddy Kankanala | 929cc00 | 2025-04-08 15:05:21 +0530 | [diff] [blame] | 243 | // Remove the logical device from the model |
Kent Hagerman | f5a6735 | 2020-04-30 15:15:26 -0400 | [diff] [blame] | 244 | if err := agent.ldProxy.Remove(ctx, agent.logicalDeviceID); err != nil { |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 245 | returnErr = err |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 246 | } else { |
Himani Chawla | b4c2591 | 2020-11-12 17:16:38 +0530 | [diff] [blame] | 247 | logger.Debugw(ctx, "logical-device-removed", log.Fields{"logical-device-id": agent.logicalDeviceID}) |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 248 | } |
Girish Gowdra | 3d922b6 | 2021-12-06 15:27:02 +0530 | [diff] [blame] | 249 | |
Kent Hagerman | fa9d6d4 | 2020-05-25 11:49:40 -0400 | [diff] [blame] | 250 | // TODO: remove all entries from all loaders |
| 251 | // TODO: don't allow any more modifications to flows/groups/meters/ports or to any logical device field |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 252 | |
Sridhar Ravindra | 7cb6eda | 2024-12-06 07:53:51 +0530 | [diff] [blame] | 253 | close(agent.exitChannel) |
Kent Hagerman | 4f355f5 | 2020-03-30 16:01:33 -0400 | [diff] [blame] | 254 | agent.stopped = true |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 255 | |
Himani Chawla | b4c2591 | 2020-11-12 17:16:38 +0530 | [diff] [blame] | 256 | logger.Info(ctx, "logical-device-agent-stopped") |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 257 | }) |
| 258 | return returnErr |
khenaidoo | 4d4802d | 2018-10-04 21:59:49 -0400 | [diff] [blame] | 259 | } |
| 260 | |
Kent Hagerman | cba2f30 | 2020-07-28 13:37:36 -0400 | [diff] [blame] | 261 | // GetLogicalDeviceReadOnly returns the latest logical device data |
| 262 | func (agent *LogicalAgent) GetLogicalDeviceReadOnly(ctx context.Context) (*voltha.LogicalDevice, error) { |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 263 | if err := agent.requestQueue.WaitForGreenLight(ctx); err != nil { |
| 264 | return nil, err |
| 265 | } |
| 266 | defer agent.requestQueue.RequestComplete() |
Kent Hagerman | cba2f30 | 2020-07-28 13:37:36 -0400 | [diff] [blame] | 267 | return agent.logicalDevice, nil |
khenaidoo | 92e62c5 | 2018-10-03 14:02:54 -0400 | [diff] [blame] | 268 | } |
| 269 | |
Gamze Abaka | fac8c19 | 2021-06-28 12:04:32 +0000 | [diff] [blame] | 270 | func (agent *LogicalAgent) addFlowsAndGroupsToDevices(ctx context.Context, deviceRules *fu.DeviceRules) []coreutils.Response { |
| 271 | logger.Debugw(ctx, "send-add-flows-to-device-manager", log.Fields{"logical-device-id": agent.logicalDeviceID, "device-rules": deviceRules}) |
khenaidoo | 19d7b63 | 2018-10-30 10:49:50 -0400 | [diff] [blame] | 272 | |
Kent Hagerman | 8da2f1e | 2019-11-25 17:28:09 -0500 | [diff] [blame] | 273 | responses := make([]coreutils.Response, 0) |
npujar | 1d86a52 | 2019-11-14 17:11:16 +0530 | [diff] [blame] | 274 | for deviceID, value := range deviceRules.GetRules() { |
Kent Hagerman | 8da2f1e | 2019-11-25 17:28:09 -0500 | [diff] [blame] | 275 | response := coreutils.NewResponse() |
| 276 | responses = append(responses, response) |
| 277 | go func(deviceId string, value *fu.FlowsAndGroups) { |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 278 | subCtx, cancel := context.WithTimeout(log.WithSpanFromContext(context.Background(), ctx), agent.internalTimeout) |
Himani Chawla | b4c2591 | 2020-11-12 17:16:38 +0530 | [diff] [blame] | 279 | subCtx = coreutils.WithRPCMetadataFromContext(subCtx, ctx) |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 280 | defer cancel() |
Gamze Abaka | fac8c19 | 2021-06-28 12:04:32 +0000 | [diff] [blame] | 281 | |
| 282 | flowMeterConfig, err := agent.GetMeterConfig(ctx, value.ListFlows()) |
| 283 | if err != nil { |
| 284 | logger.Error(ctx, "meter-referred-in-flow-not-present") |
| 285 | response.Error(status.Errorf(codes.NotFound, "meter-referred-in-flow-not-present")) |
| 286 | return |
| 287 | } |
khenaidoo | 0db4c81 | 2020-05-27 15:27:30 -0400 | [diff] [blame] | 288 | start := time.Now() |
Gamze Abaka | fac8c19 | 2021-06-28 12:04:32 +0000 | [diff] [blame] | 289 | if err := agent.deviceMgr.addFlowsAndGroups(subCtx, deviceId, value.ListFlows(), value.ListGroups(), toMetadata(flowMeterConfig)); err != nil { |
Matteo Scandolo | 45e514a | 2020-08-05 15:27:10 -0700 | [diff] [blame] | 290 | logger.Errorw(ctx, "flow-add-failed", log.Fields{ |
divyadesai | cb8b59d | 2020-08-18 09:55:47 +0000 | [diff] [blame] | 291 | "device-id": deviceId, |
Matteo Scandolo | 45e514a | 2020-08-05 15:27:10 -0700 | [diff] [blame] | 292 | "error": err, |
| 293 | "wait-time": time.Since(start), |
| 294 | "flows": value.ListFlows(), |
| 295 | "groups": value.ListGroups(), |
| 296 | }) |
Kent Hagerman | 8da2f1e | 2019-11-25 17:28:09 -0500 | [diff] [blame] | 297 | response.Error(status.Errorf(codes.Internal, "flow-add-failed: %s", deviceId)) |
khenaidoo | 0458db6 | 2019-06-20 08:50:36 -0400 | [diff] [blame] | 298 | } |
Kent Hagerman | 8da2f1e | 2019-11-25 17:28:09 -0500 | [diff] [blame] | 299 | response.Done() |
npujar | 1d86a52 | 2019-11-14 17:11:16 +0530 | [diff] [blame] | 300 | }(deviceID, value) |
khenaidoo | 19d7b63 | 2018-10-30 10:49:50 -0400 | [diff] [blame] | 301 | } |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 302 | // Return responses (an array of channels) for the caller to wait for a response from the far end. |
| 303 | return responses |
khenaidoo | 0458db6 | 2019-06-20 08:50:36 -0400 | [diff] [blame] | 304 | } |
khenaidoo | 19d7b63 | 2018-10-30 10:49:50 -0400 | [diff] [blame] | 305 | |
Gamze Abaka | fac8c19 | 2021-06-28 12:04:32 +0000 | [diff] [blame] | 306 | func (agent *LogicalAgent) deleteFlowsAndGroupsFromDevices(ctx context.Context, deviceRules *fu.DeviceRules, mod *ofp.OfpFlowMod) []coreutils.Response { |
divyadesai | cb8b59d | 2020-08-18 09:55:47 +0000 | [diff] [blame] | 307 | logger.Debugw(ctx, "send-delete-flows-to-device-manager", log.Fields{"logical-device-id": agent.logicalDeviceID}) |
khenaidoo | 0458db6 | 2019-06-20 08:50:36 -0400 | [diff] [blame] | 308 | |
Kent Hagerman | 8da2f1e | 2019-11-25 17:28:09 -0500 | [diff] [blame] | 309 | responses := make([]coreutils.Response, 0) |
npujar | 1d86a52 | 2019-11-14 17:11:16 +0530 | [diff] [blame] | 310 | for deviceID, value := range deviceRules.GetRules() { |
Kent Hagerman | 8da2f1e | 2019-11-25 17:28:09 -0500 | [diff] [blame] | 311 | response := coreutils.NewResponse() |
| 312 | responses = append(responses, response) |
| 313 | go func(deviceId string, value *fu.FlowsAndGroups) { |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 314 | subCtx, cancel := context.WithTimeout(log.WithSpanFromContext(context.Background(), ctx), agent.internalTimeout) |
Himani Chawla | b4c2591 | 2020-11-12 17:16:38 +0530 | [diff] [blame] | 315 | subCtx = coreutils.WithRPCMetadataFromContext(subCtx, ctx) |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 316 | defer cancel() |
Gamze Abaka | fac8c19 | 2021-06-28 12:04:32 +0000 | [diff] [blame] | 317 | |
| 318 | flowMeterConfig, err := agent.GetMeterConfig(ctx, value.ListFlows()) |
| 319 | if err != nil { |
| 320 | logger.Error(ctx, "meter-referred-in-flow-not-present") |
| 321 | response.Error(status.Errorf(codes.NotFound, "meter-referred-in-flow-not-present")) |
| 322 | return |
| 323 | } |
khenaidoo | 0db4c81 | 2020-05-27 15:27:30 -0400 | [diff] [blame] | 324 | start := time.Now() |
Gamze Abaka | fac8c19 | 2021-06-28 12:04:32 +0000 | [diff] [blame] | 325 | if err := agent.deviceMgr.deleteFlowsAndGroups(subCtx, deviceId, value.ListFlows(), value.ListGroups(), toMetadata(flowMeterConfig)); err != nil { |
Rohan Agrawal | 31f2180 | 2020-06-12 05:38:46 +0000 | [diff] [blame] | 326 | logger.Errorw(ctx, "flows-and-groups-delete-failed", log.Fields{ |
Matteo Scandolo | 367162b | 2020-06-22 15:07:33 -0700 | [diff] [blame] | 327 | "device-id": deviceId, |
| 328 | "error": err, |
| 329 | "flow-cookie": mod.Cookie, |
| 330 | "wait-time": time.Since(start), |
| 331 | }) |
Kent Hagerman | 8da2f1e | 2019-11-25 17:28:09 -0500 | [diff] [blame] | 332 | response.Error(status.Errorf(codes.Internal, "flow-delete-failed: %s", deviceId)) |
khenaidoo | 0458db6 | 2019-06-20 08:50:36 -0400 | [diff] [blame] | 333 | } |
Kent Hagerman | 8da2f1e | 2019-11-25 17:28:09 -0500 | [diff] [blame] | 334 | response.Done() |
npujar | 1d86a52 | 2019-11-14 17:11:16 +0530 | [diff] [blame] | 335 | }(deviceID, value) |
khenaidoo | 0458db6 | 2019-06-20 08:50:36 -0400 | [diff] [blame] | 336 | } |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 337 | return responses |
khenaidoo | 0458db6 | 2019-06-20 08:50:36 -0400 | [diff] [blame] | 338 | } |
| 339 | |
khenaidoo | 9beaaf1 | 2021-10-19 17:32:01 -0400 | [diff] [blame] | 340 | func (agent *LogicalAgent) updateFlowsAndGroupsOfDevice(ctx context.Context, deviceRules *fu.DeviceRules, flowMetadata *ofp.FlowMetadata) []coreutils.Response { |
divyadesai | cb8b59d | 2020-08-18 09:55:47 +0000 | [diff] [blame] | 341 | logger.Debugw(ctx, "send-update-flows-to-device-manager", log.Fields{"logical-device-id": agent.logicalDeviceID}) |
khenaidoo | 0458db6 | 2019-06-20 08:50:36 -0400 | [diff] [blame] | 342 | |
Kent Hagerman | 8da2f1e | 2019-11-25 17:28:09 -0500 | [diff] [blame] | 343 | responses := make([]coreutils.Response, 0) |
npujar | 1d86a52 | 2019-11-14 17:11:16 +0530 | [diff] [blame] | 344 | for deviceID, value := range deviceRules.GetRules() { |
Kent Hagerman | 8da2f1e | 2019-11-25 17:28:09 -0500 | [diff] [blame] | 345 | response := coreutils.NewResponse() |
| 346 | responses = append(responses, response) |
| 347 | go func(deviceId string, value *fu.FlowsAndGroups) { |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 348 | subCtx, cancel := context.WithTimeout(log.WithSpanFromContext(context.Background(), ctx), agent.internalTimeout) |
Himani Chawla | b4c2591 | 2020-11-12 17:16:38 +0530 | [diff] [blame] | 349 | subCtx = coreutils.WithRPCMetadataFromContext(subCtx, ctx) |
| 350 | |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 351 | defer cancel() |
khenaidoo | 0db4c81 | 2020-05-27 15:27:30 -0400 | [diff] [blame] | 352 | if err := agent.deviceMgr.updateFlowsAndGroups(subCtx, deviceId, value.ListFlows(), value.ListGroups(), flowMetadata); err != nil { |
divyadesai | cb8b59d | 2020-08-18 09:55:47 +0000 | [diff] [blame] | 353 | logger.Errorw(ctx, "flow-update-failed", log.Fields{"device-id": deviceId, "error": err}) |
Kent Hagerman | 8da2f1e | 2019-11-25 17:28:09 -0500 | [diff] [blame] | 354 | response.Error(status.Errorf(codes.Internal, "flow-update-failed: %s", deviceId)) |
khenaidoo | 0458db6 | 2019-06-20 08:50:36 -0400 | [diff] [blame] | 355 | } |
Kent Hagerman | 8da2f1e | 2019-11-25 17:28:09 -0500 | [diff] [blame] | 356 | response.Done() |
npujar | 1d86a52 | 2019-11-14 17:11:16 +0530 | [diff] [blame] | 357 | }(deviceID, value) |
khenaidoo | 0458db6 | 2019-06-20 08:50:36 -0400 | [diff] [blame] | 358 | } |
khenaidoo | 442e7c7 | 2020-03-10 16:13:48 -0400 | [diff] [blame] | 359 | return responses |
khenaidoo | 19d7b63 | 2018-10-30 10:49:50 -0400 | [diff] [blame] | 360 | } |
| 361 | |
Gamze Abaka | fac8c19 | 2021-06-28 12:04:32 +0000 | [diff] [blame] | 362 | func (agent *LogicalAgent) deleteFlowsFromParentDevice(ctx context.Context, flows map[uint64]*ofp.OfpFlowStats, mod *ofp.OfpFlowMod) []coreutils.Response { |
Rohan Agrawal | 31f2180 | 2020-06-12 05:38:46 +0000 | [diff] [blame] | 363 | logger.Debugw(ctx, "deleting-flows-from-parent-device", log.Fields{"logical-device-id": agent.logicalDeviceID, "flows": flows}) |
khenaidoo | 787224a | 2020-04-16 18:08:47 -0400 | [diff] [blame] | 364 | responses := make([]coreutils.Response, 0) |
Kent Hagerman | 433a31a | 2020-05-20 19:04:48 -0400 | [diff] [blame] | 365 | for _, flow := range flows { |
khenaidoo | 787224a | 2020-04-16 18:08:47 -0400 | [diff] [blame] | 366 | response := coreutils.NewResponse() |
| 367 | responses = append(responses, response) |
Gamze Abaka | fac8c19 | 2021-06-28 12:04:32 +0000 | [diff] [blame] | 368 | |
| 369 | flowMeterConfig, err := agent.GetMeterConfig(ctx, []*ofp.OfpFlowStats{flow}) |
| 370 | if err != nil { |
| 371 | logger.Error(ctx, "meter-referred-in-flow-not-present") |
| 372 | response.Error(status.Errorf(codes.NotFound, "meter-referred-in-flow-not-present")) |
| 373 | return responses |
| 374 | } |
khenaidoo | 787224a | 2020-04-16 18:08:47 -0400 | [diff] [blame] | 375 | uniPort, err := agent.getUNILogicalPortNo(flow) |
| 376 | if err != nil { |
divyadesai | cb8b59d | 2020-08-18 09:55:47 +0000 | [diff] [blame] | 377 | logger.Error(ctx, "no-uni-port-in-flow", log.Fields{"device-id": agent.rootDeviceID, "flow": flow, "error": err}) |
khenaidoo | 787224a | 2020-04-16 18:08:47 -0400 | [diff] [blame] | 378 | response.Error(err) |
| 379 | response.Done() |
| 380 | continue |
| 381 | } |
Rohan Agrawal | 31f2180 | 2020-06-12 05:38:46 +0000 | [diff] [blame] | 382 | logger.Debugw(ctx, "uni-port", log.Fields{"flows": flows, "uni-port": uniPort}) |
khenaidoo | 9beaaf1 | 2021-10-19 17:32:01 -0400 | [diff] [blame] | 383 | go func(uniPort uint32, metadata *ofp.FlowMetadata) { |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 384 | subCtx, cancel := context.WithTimeout(log.WithSpanFromContext(context.Background(), ctx), agent.internalTimeout) |
Himani Chawla | b4c2591 | 2020-11-12 17:16:38 +0530 | [diff] [blame] | 385 | subCtx = coreutils.WithRPCMetadataFromContext(subCtx, ctx) |
| 386 | |
khenaidoo | 787224a | 2020-04-16 18:08:47 -0400 | [diff] [blame] | 387 | defer cancel() |
khenaidoo | 0db4c81 | 2020-05-27 15:27:30 -0400 | [diff] [blame] | 388 | if err := agent.deviceMgr.deleteParentFlows(subCtx, agent.rootDeviceID, uniPort, metadata); err != nil { |
Rohan Agrawal | 31f2180 | 2020-06-12 05:38:46 +0000 | [diff] [blame] | 389 | logger.Error(ctx, "flow-delete-failed-from-parent-device", log.Fields{ |
Matteo Scandolo | 367162b | 2020-06-22 15:07:33 -0700 | [diff] [blame] | 390 | "device-id": agent.rootDeviceID, |
| 391 | "error": err, |
| 392 | "flow-cookie": mod.Cookie, |
| 393 | }) |
khenaidoo | 787224a | 2020-04-16 18:08:47 -0400 | [diff] [blame] | 394 | response.Error(status.Errorf(codes.Internal, "flow-delete-failed: %s %v", agent.rootDeviceID, err)) |
| 395 | } |
| 396 | response.Done() |
Gamze Abaka | fac8c19 | 2021-06-28 12:04:32 +0000 | [diff] [blame] | 397 | }(uniPort, toMetadata(flowMeterConfig)) |
khenaidoo | 787224a | 2020-04-16 18:08:47 -0400 | [diff] [blame] | 398 | } |
| 399 | return responses |
| 400 | } |
| 401 | |
Kent Hagerman | 2b21604 | 2020-04-03 18:28:56 -0400 | [diff] [blame] | 402 | func (agent *LogicalAgent) packetOut(ctx context.Context, packet *ofp.OfpPacketOut) { |
Matteo Scandolo | 45e514a | 2020-08-05 15:27:10 -0700 | [diff] [blame] | 403 | if logger.V(log.InfoLevel) { |
| 404 | logger.Infow(ctx, "packet-out", log.Fields{ |
Himani Chawla | b4c2591 | 2020-11-12 17:16:38 +0530 | [diff] [blame] | 405 | "packet": hex.EncodeToString(packet.Data), |
| 406 | "in-port": packet.GetInPort(), |
Matteo Scandolo | 45e514a | 2020-08-05 15:27:10 -0700 | [diff] [blame] | 407 | }) |
| 408 | } |
khenaidoo | 68c930b | 2019-05-13 11:46:51 -0400 | [diff] [blame] | 409 | outPort := fu.GetPacketOutPort(packet) |
Akash Reddy Kankanala | 929cc00 | 2025-04-08 15:05:21 +0530 | [diff] [blame] | 410 | // frame := packet.GetData() |
| 411 | // TODO: Use a channel between the logical agent and the device agent |
npujar | 467fe75 | 2020-01-16 20:17:45 +0530 | [diff] [blame] | 412 | if err := agent.deviceMgr.packetOut(ctx, agent.rootDeviceID, outPort, packet); err != nil { |
Himani Chawla | b4c2591 | 2020-11-12 17:16:38 +0530 | [diff] [blame] | 413 | logger.Error(ctx, "packet-out-failed", log.Fields{"logical-device-id": agent.rootDeviceID}) |
khenaidoo | ca30132 | 2019-01-09 23:06:32 -0500 | [diff] [blame] | 414 | } |
khenaidoo | fdbad6e | 2018-11-06 22:26:38 -0500 | [diff] [blame] | 415 | } |
| 416 | |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 417 | func (agent *LogicalAgent) packetIn(ctx context.Context, port uint32, packet []byte) { |
Matteo Scandolo | 45e514a | 2020-08-05 15:27:10 -0700 | [diff] [blame] | 418 | if logger.V(log.InfoLevel) { |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 419 | logger.Infow(ctx, "packet-in", log.Fields{ |
| 420 | "port": port, |
| 421 | "packet": hex.EncodeToString(packet), |
Matteo Scandolo | 45e514a | 2020-08-05 15:27:10 -0700 | [diff] [blame] | 422 | }) |
| 423 | } |
| 424 | |
khenaidoo | 68c930b | 2019-05-13 11:46:51 -0400 | [diff] [blame] | 425 | packetIn := fu.MkPacketIn(port, packet) |
khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 426 | agent.ldeviceMgr.SendPacketIn(ctx, agent.logicalDeviceID, packetIn) |
Rohan Agrawal | 31f2180 | 2020-06-12 05:38:46 +0000 | [diff] [blame] | 427 | logger.Debugw(ctx, "sending-packet-in", log.Fields{"packet": hex.EncodeToString(packetIn.Data)}) |
khenaidoo | fdbad6e | 2018-11-06 22:26:38 -0500 | [diff] [blame] | 428 | } |