blob: 873a4646b1875da0319979d71ad46ed3d87d742d [file] [log] [blame]
Matteo Scandolo40e067f2019-10-16 16:59:41 -07001/*
Joey Armstrong3881b732022-12-27 07:55:37 -05002 * Copyright 2018-2023 Open Networking Foundation (ONF) and the ONF Contributors
Matteo Scandolo40e067f2019-10-16 16:59:41 -07003
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 */
16
17package devices
18
19import (
20 "context"
Matteo Scandolo4a036262020-08-17 15:56:13 -070021 "encoding/hex"
Matteo Scandolo40e067f2019-10-16 16:59:41 -070022 "fmt"
Matteo Scandolof9d43412021-01-12 11:11:34 -080023 "github.com/opencord/bbsim/internal/bbsim/types"
Shrey Baid688b4242020-07-10 20:40:10 +053024 "io"
25 "reflect"
26 "time"
27
Matteo Scandolo40e067f2019-10-16 16:59:41 -070028 "github.com/google/gopacket"
29 "github.com/google/gopacket/layers"
30 "github.com/opencord/bbsim/internal/bbsim/devices"
31 "github.com/opencord/bbsim/internal/bbsim/packetHandlers"
32 "github.com/opencord/bbsim/internal/common"
David K. Bainbridgec415efe2021-08-19 13:05:21 +000033 "github.com/opencord/voltha-protos/v5/go/openolt"
Matteo Scandolo40e067f2019-10-16 16:59:41 -070034 log "github.com/sirupsen/logrus"
35 "google.golang.org/grpc"
Matteo Scandolo40e067f2019-10-16 16:59:41 -070036)
37
38type OltMock struct {
Matteo Scandolo583f17d2020-02-13 10:35:17 -080039 LastUsedOnuId map[uint32]uint32
40 Olt *devices.OltDevice
41 BBSimIp string
42 BBSimPort string
43 BBSimApiPort string
Matteo Scandolo40e067f2019-10-16 16:59:41 -070044
Matteo Scandolo8a574812021-05-20 15:18:53 -070045 conn *grpc.ClientConn
46 Client *openolt.OpenoltClient
Matteo Scandolo40e067f2019-10-16 16:59:41 -070047
48 TargetOnus int
49 CompletedOnus int // Number of ONUs that have received a DHCPAck
50}
51
Matteo Scandolo9ddb3a92021-04-14 16:16:20 -070052type MockStream struct {
53 grpc.ServerStream
54}
55
56func (*MockStream) Send(ind *openolt.Indication) error {
57 return nil
58}
59
60func (*MockStream) Context() context.Context {
61 return context.Background()
62}
63
Matteo Scandolo40e067f2019-10-16 16:59:41 -070064// trigger an enable call and start the same listeners on the gRPC stream that VOLTHA would create
65// this method is blocking
66func (o *OltMock) Start() {
67 log.Info("Starting Mock OLT")
68
69 for _, pon := range o.Olt.Pons {
70 for _, onu := range pon.Onus {
Matteo Scandolod32c3822019-11-26 15:57:46 -070071 if err := onu.InternalState.Event("initialize"); err != nil {
72 log.Fatalf("Error initializing ONU: %v", err)
73 }
Matteo Scandolo4a036262020-08-17 15:56:13 -070074 log.Debugf("Created ONU: %s", onu.Sn())
Matteo Scandolo40e067f2019-10-16 16:59:41 -070075 }
76 }
77
78 client, conn := Connect(o.BBSimIp, o.BBSimPort)
79 o.conn = conn
Matteo Scandolo8a574812021-05-20 15:18:53 -070080 o.Client = &client
Matteo Scandolo40e067f2019-10-16 16:59:41 -070081 defer conn.Close()
82
83 deviceInfo, err := o.getDeviceInfo(client)
84
85 if err != nil {
86 log.WithFields(log.Fields{
87 "error": err,
88 }).Fatal("Can't read device info")
89 }
90
91 log.WithFields(log.Fields{
92 "Vendor": deviceInfo.Vendor,
93 "Model": deviceInfo.Model,
94 "DeviceSerialNumber": deviceInfo.DeviceSerialNumber,
95 "PonPorts": deviceInfo.PonPorts,
96 }).Info("Retrieved device info")
97
98 o.readIndications(client)
99
100}
101
102func (o *OltMock) getDeviceInfo(client openolt.OpenoltClient) (*openolt.DeviceInfo, error) {
103 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
104 defer cancel()
105
106 return client.GetDeviceInfo(ctx, new(openolt.Empty))
107}
108
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700109func (o *OltMock) readIndications(client openolt.OpenoltClient) {
110 defer func() {
111 log.Info("OLT readIndications done")
112 }()
113
114 // Tell the OLT to start sending indications
115 indications, err := client.EnableIndication(context.Background(), new(openolt.Empty))
116 if err != nil {
117 log.WithFields(log.Fields{
118 "error": err,
119 }).Error("Failed to enable indication stream")
120 return
121 }
122
123 // listen for indications
124 for {
125 indication, err := indications.Recv()
126 if err == io.EOF {
127 break
128 }
129 if err != nil {
130
131 // the connection is closed once we have sent the DHCP_ACK packet to all of the ONUs
132 // it means BBR completed, it's not an error
133
134 log.WithFields(log.Fields{
135 "error": err,
136 }).Debug("Failed to read from indications")
137 break
138 }
139
140 o.handleIndication(client, indication)
141 }
142}
143
144func (o *OltMock) handleIndication(client openolt.OpenoltClient, indication *openolt.Indication) {
145 switch indication.Data.(type) {
146 case *openolt.Indication_OltInd:
147 log.Info("Received Indication_OltInd")
148 case *openolt.Indication_IntfInd:
149 log.Info("Received Indication_IntfInd")
150 case *openolt.Indication_IntfOperInd:
151 log.Info("Received Indication_IntfOperInd")
152 case *openolt.Indication_OnuDiscInd:
153 onuDiscInd := indication.GetOnuDiscInd()
154 o.handleOnuDiscIndication(client, onuDiscInd)
155 case *openolt.Indication_OnuInd:
156 onuInd := indication.GetOnuInd()
157 o.handleOnuIndication(client, onuInd)
158 case *openolt.Indication_OmciInd:
159 omciIndication := indication.GetOmciInd()
160 o.handleOmciIndication(client, omciIndication)
161 case *openolt.Indication_PktInd:
162 pktIndication := indication.GetPktInd()
163 o.handlePktIndication(client, pktIndication)
164 case *openolt.Indication_PortStats:
165 case *openolt.Indication_FlowStats:
166 case *openolt.Indication_AlarmInd:
167 default:
168 log.WithFields(log.Fields{
169 "data": indication.Data,
170 "type": reflect.TypeOf(indication.Data),
171 }).Warn("Indication unsupported")
172 }
173}
174
175func (o *OltMock) handleOnuDiscIndication(client openolt.OpenoltClient, onuDiscInd *openolt.OnuDiscIndication) {
176 log.WithFields(log.Fields{
177 "IntfId": onuDiscInd.IntfId,
178 "SerialNumber": common.OnuSnToString(onuDiscInd.SerialNumber),
179 }).Info("Received Onu discovery indication")
180
181 onu, err := o.Olt.FindOnuBySn(common.OnuSnToString(onuDiscInd.SerialNumber))
182
183 if err != nil {
184 log.WithFields(log.Fields{
185 "IntfId": onuDiscInd.IntfId,
186 "SerialNumber": common.OnuSnToString(onuDiscInd.SerialNumber),
Matteo Scandolo583f17d2020-02-13 10:35:17 -0800187 "Err": err,
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700188 }).Fatal("Cannot find ONU")
189 }
190
Matteo Scandolo583f17d2020-02-13 10:35:17 -0800191 // creating and storing ONU IDs
192 id := o.LastUsedOnuId[onuDiscInd.IntfId] + 1
193 o.LastUsedOnuId[onuDiscInd.IntfId] = o.LastUsedOnuId[onuDiscInd.IntfId] + 1
194 onu.SetID(id)
195
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700196 var pir uint32 = 1000000
197 Onu := openolt.Onu{
198 IntfId: onu.PonPortID,
Matteo Scandolo583f17d2020-02-13 10:35:17 -0800199 OnuId: id,
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700200 SerialNumber: onu.SerialNumber,
201 Pir: pir,
202 }
203
204 if _, err := client.ActivateOnu(context.Background(), &Onu); err != nil {
205 log.WithFields(log.Fields{
206 "IntfId": onuDiscInd.IntfId,
207 "SerialNumber": common.OnuSnToString(onuDiscInd.SerialNumber),
208 }).Error("Failed to activate ONU")
209 }
210}
211
212func (o *OltMock) handleOnuIndication(client openolt.OpenoltClient, onuInd *openolt.OnuIndication) {
213 log.WithFields(log.Fields{
214 "IntfId": onuInd.IntfId,
215 "SerialNumber": common.OnuSnToString(onuInd.SerialNumber),
216 }).Info("Received Onu indication")
217
218 onu, err := o.Olt.FindOnuBySn(common.OnuSnToString(onuInd.SerialNumber))
219
220 if err != nil {
221 log.WithFields(log.Fields{
222 "IntfId": onuInd.IntfId,
223 "SerialNumber": common.OnuSnToString(onuInd.SerialNumber),
224 }).Fatal("Cannot find ONU")
225 }
226
David Bainbridge103cf022019-12-16 20:11:35 +0000227 ctx, cancel := context.WithCancel(context.TODO())
Matteo Scandolo9ddb3a92021-04-14 16:16:20 -0700228 // NOTE we need to create a fake stream for ProcessOnuMessages
229 // as it listen on the context to cancel the loop
230 // In the BBR case it's not used for anything else
231 mockStream := MockStream{}
232 go onu.ProcessOnuMessages(ctx, &mockStream, client)
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700233
234 go func() {
235
236 defer func() {
237 log.WithFields(log.Fields{
238 "onuSn": common.OnuSnToString(onuInd.SerialNumber),
239 "CompletedOnus": o.CompletedOnus,
240 "TargetOnus": o.TargetOnus,
241 }).Debugf("Onu done")
242
Matteo Scandolo569e7172019-12-20 11:51:51 -0800243 // close the ONU channel
244 cancel()
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700245 }()
246
247 for message := range onu.DoneChannel {
Shrey Baid688b4242020-07-10 20:40:10 +0530248 if message {
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700249 o.CompletedOnus++
250 if o.CompletedOnus == o.TargetOnus {
251 // NOTE once all the ONUs are completed, exit
252 // closing the connection is not the most elegant way,
253 // but I haven't found any other way to stop
254 // the indications.Recv() infinite loop
255 log.Info("Simulation Done")
256 ValidateAndClose(o)
257 }
258
259 break
260 }
261 }
262
263 }()
264
265 // TODO change the state instead of calling an ONU method from here
266 onu.StartOmci(client)
267}
268
269func (o *OltMock) handleOmciIndication(client openolt.OpenoltClient, omciInd *openolt.OmciIndication) {
270
271 pon, err := o.Olt.GetPonById(omciInd.IntfId)
272 if err != nil {
273 log.WithFields(log.Fields{
274 "OnuId": omciInd.OnuId,
275 "IntfId": omciInd.IntfId,
276 "err": err,
277 }).Fatal("Can't find PonPort")
278 }
279 onu, _ := pon.GetOnuById(omciInd.OnuId)
280 if err != nil {
281 log.WithFields(log.Fields{
282 "OnuId": omciInd.OnuId,
283 "IntfId": omciInd.IntfId,
284 "err": err,
285 }).Fatal("Can't find Onu")
286 }
287
288 log.WithFields(log.Fields{
289 "IntfId": onu.PonPortID,
290 "OnuId": onu.ID,
291 "OnuSn": onu.Sn(),
292 "Pkt": omciInd.Pkt,
293 }).Trace("Received Onu omci indication")
294
Matteo Scandolof9d43412021-01-12 11:11:34 -0800295 msg := types.Message{
296 Type: types.OmciIndication,
297 Data: types.OmciIndicationMessage{
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700298 OnuSN: onu.SerialNumber,
299 OnuID: onu.ID,
300 OmciInd: omciInd,
301 },
302 }
303 onu.Channel <- msg
304}
305
Matteo Scandolo8a574812021-05-20 15:18:53 -0700306// packets arriving from the ONU and received in VOLTHA
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700307func (o *OltMock) handlePktIndication(client openolt.OpenoltClient, pktIndication *openolt.PacketIndication) {
308
309 pkt := gopacket.NewPacket(pktIndication.Pkt, layers.LayerTypeEthernet, gopacket.Default)
310
Matteo Scandolo618a6582020-09-09 12:21:29 -0700311 pktType, err := packetHandlers.GetPktType(pkt)
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700312
313 if err != nil {
314 log.Warnf("Ignoring packet as it's neither EAPOL or DHCP")
315 return
316 }
317
318 log.WithFields(log.Fields{
319 "IntfType": pktIndication.IntfType,
320 "IntfId": pktIndication.IntfId,
321 "GemportId": pktIndication.GemportId,
322 "FlowId": pktIndication.FlowId,
323 "PortNo": pktIndication.PortNo,
324 "Cookie": pktIndication.Cookie,
325 "pktType": pktType,
326 }).Trace("Received PktIndication")
327
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700328 if pktIndication.IntfType == "nni" {
329 // This is an packet that is arriving from the NNI and needs to be sent to an ONU
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700330
Matteo Scandolo4a036262020-08-17 15:56:13 -0700331 onuMac, err := packetHandlers.GetDstMacAddressFromPacket(pkt)
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700332
333 if err != nil {
334 log.WithFields(log.Fields{
Matteo Scandolo4a036262020-08-17 15:56:13 -0700335 "IntfType": "nni",
336 "Pkt": hex.EncodeToString(pkt.Data()),
337 }).Fatal("Can't find Dst MacAddress in packet")
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700338 }
339
Matteo Scandolo4a036262020-08-17 15:56:13 -0700340 s, err := o.Olt.FindServiceByMacAddress(onuMac)
341 if err != nil {
342 log.WithFields(log.Fields{
343 "IntfType": "nni",
344 "Pkt": hex.EncodeToString(pkt.Data()),
345 "MacAddress": onuMac.String(),
346 }).Fatal("Can't find ONU with MacAddress")
347 }
348
349 service := s.(*devices.Service)
Matteo Scandolo8a574812021-05-20 15:18:53 -0700350 onu := service.UniPort.Onu
Matteo Scandolo4a036262020-08-17 15:56:13 -0700351
Matteo Scandolof9d43412021-01-12 11:11:34 -0800352 msg := types.Message{
353 Type: types.OnuPacketIn,
354 Data: types.OnuPacketMessage{
Matteo Scandolo4a036262020-08-17 15:56:13 -0700355 IntfId: pktIndication.IntfId,
356 OnuId: onu.ID,
357 Packet: pkt,
358 Type: pktType,
359 GemPortId: pktIndication.GemportId,
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700360 },
361 }
362 // NOTE we send it on the ONU channel so that is handled as all the others packets in a separate thread
363 onu.Channel <- msg
364 } else {
365 // TODO a very similar construct is used in many places,
366 // abstract this in an OLT method
367 pon, err := o.Olt.GetPonById(pktIndication.IntfId)
368 if err != nil {
369 log.WithFields(log.Fields{
370 "OnuId": pktIndication.PortNo,
371 "IntfId": pktIndication.IntfId,
372 "err": err,
373 }).Fatal("Can't find PonPort")
374 }
375 onu, err := pon.GetOnuById(pktIndication.PortNo)
376 if err != nil {
377 log.WithFields(log.Fields{
378 "OnuId": pktIndication.PortNo,
379 "IntfId": pktIndication.IntfId,
380 "err": err,
381 }).Fatal("Can't find Onu")
382 }
383 // NOTE when we push the EAPOL flow we set the PortNo = OnuId for convenience sake
384 // BBsim responds setting the port number that was sent with the flow
Matteo Scandolof9d43412021-01-12 11:11:34 -0800385 msg := types.Message{
386 Type: types.OnuPacketIn,
387 Data: types.OnuPacketMessage{
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700388 IntfId: pktIndication.IntfId,
389 OnuId: pktIndication.PortNo,
390 Packet: pkt,
391 Type: pktType,
392 },
393 }
394 onu.Channel <- msg
395 }
396}
397
398// TODO Move in a different file
399func Connect(ip string, port string) (openolt.OpenoltClient, *grpc.ClientConn) {
400 server := fmt.Sprintf("%s:%s", ip, port)
401 conn, err := grpc.Dial(server, grpc.WithInsecure())
402
403 if err != nil {
404 log.Fatalf("did not connect: %v", err)
405 return nil, conn
406 }
407 return openolt.NewOpenoltClient(conn), conn
408}