blob: b40bf583a7cbaff3a60dc7ea28be6fe34f822ff2 [file] [log] [blame]
Matteo Scandolo40e067f2019-10-16 16:59:41 -07001/*
2 * Copyright 2018-present Open Networking Foundation
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 */
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"
Matteo Scandolo4f4ac792020-10-01 16:33:21 -070033 "github.com/opencord/voltha-protos/v4/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
45 conn *grpc.ClientConn
46
47 TargetOnus int
48 CompletedOnus int // Number of ONUs that have received a DHCPAck
49}
50
Matteo Scandolo9ddb3a92021-04-14 16:16:20 -070051type MockStream struct {
52 grpc.ServerStream
53}
54
55func (*MockStream) Send(ind *openolt.Indication) error {
56 return nil
57}
58
59func (*MockStream) Context() context.Context {
60 return context.Background()
61}
62
Matteo Scandolo40e067f2019-10-16 16:59:41 -070063// trigger an enable call and start the same listeners on the gRPC stream that VOLTHA would create
64// this method is blocking
65func (o *OltMock) Start() {
66 log.Info("Starting Mock OLT")
67
68 for _, pon := range o.Olt.Pons {
69 for _, onu := range pon.Onus {
Matteo Scandolod32c3822019-11-26 15:57:46 -070070 if err := onu.InternalState.Event("initialize"); err != nil {
71 log.Fatalf("Error initializing ONU: %v", err)
72 }
Matteo Scandolo4a036262020-08-17 15:56:13 -070073 log.Debugf("Created ONU: %s", onu.Sn())
Matteo Scandolo40e067f2019-10-16 16:59:41 -070074 }
75 }
76
77 client, conn := Connect(o.BBSimIp, o.BBSimPort)
78 o.conn = conn
79 defer conn.Close()
80
81 deviceInfo, err := o.getDeviceInfo(client)
82
83 if err != nil {
84 log.WithFields(log.Fields{
85 "error": err,
86 }).Fatal("Can't read device info")
87 }
88
89 log.WithFields(log.Fields{
90 "Vendor": deviceInfo.Vendor,
91 "Model": deviceInfo.Model,
92 "DeviceSerialNumber": deviceInfo.DeviceSerialNumber,
93 "PonPorts": deviceInfo.PonPorts,
94 }).Info("Retrieved device info")
95
96 o.readIndications(client)
97
98}
99
100func (o *OltMock) getDeviceInfo(client openolt.OpenoltClient) (*openolt.DeviceInfo, error) {
101 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
102 defer cancel()
103
104 return client.GetDeviceInfo(ctx, new(openolt.Empty))
105}
106
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700107func (o *OltMock) readIndications(client openolt.OpenoltClient) {
108 defer func() {
109 log.Info("OLT readIndications done")
110 }()
111
112 // Tell the OLT to start sending indications
113 indications, err := client.EnableIndication(context.Background(), new(openolt.Empty))
114 if err != nil {
115 log.WithFields(log.Fields{
116 "error": err,
117 }).Error("Failed to enable indication stream")
118 return
119 }
120
121 // listen for indications
122 for {
123 indication, err := indications.Recv()
124 if err == io.EOF {
125 break
126 }
127 if err != nil {
128
129 // the connection is closed once we have sent the DHCP_ACK packet to all of the ONUs
130 // it means BBR completed, it's not an error
131
132 log.WithFields(log.Fields{
133 "error": err,
134 }).Debug("Failed to read from indications")
135 break
136 }
137
138 o.handleIndication(client, indication)
139 }
140}
141
142func (o *OltMock) handleIndication(client openolt.OpenoltClient, indication *openolt.Indication) {
143 switch indication.Data.(type) {
144 case *openolt.Indication_OltInd:
145 log.Info("Received Indication_OltInd")
146 case *openolt.Indication_IntfInd:
147 log.Info("Received Indication_IntfInd")
148 case *openolt.Indication_IntfOperInd:
149 log.Info("Received Indication_IntfOperInd")
150 case *openolt.Indication_OnuDiscInd:
151 onuDiscInd := indication.GetOnuDiscInd()
152 o.handleOnuDiscIndication(client, onuDiscInd)
153 case *openolt.Indication_OnuInd:
154 onuInd := indication.GetOnuInd()
155 o.handleOnuIndication(client, onuInd)
156 case *openolt.Indication_OmciInd:
157 omciIndication := indication.GetOmciInd()
158 o.handleOmciIndication(client, omciIndication)
159 case *openolt.Indication_PktInd:
160 pktIndication := indication.GetPktInd()
161 o.handlePktIndication(client, pktIndication)
162 case *openolt.Indication_PortStats:
163 case *openolt.Indication_FlowStats:
164 case *openolt.Indication_AlarmInd:
165 default:
166 log.WithFields(log.Fields{
167 "data": indication.Data,
168 "type": reflect.TypeOf(indication.Data),
169 }).Warn("Indication unsupported")
170 }
171}
172
173func (o *OltMock) handleOnuDiscIndication(client openolt.OpenoltClient, onuDiscInd *openolt.OnuDiscIndication) {
174 log.WithFields(log.Fields{
175 "IntfId": onuDiscInd.IntfId,
176 "SerialNumber": common.OnuSnToString(onuDiscInd.SerialNumber),
177 }).Info("Received Onu discovery indication")
178
179 onu, err := o.Olt.FindOnuBySn(common.OnuSnToString(onuDiscInd.SerialNumber))
180
181 if err != nil {
182 log.WithFields(log.Fields{
183 "IntfId": onuDiscInd.IntfId,
184 "SerialNumber": common.OnuSnToString(onuDiscInd.SerialNumber),
Matteo Scandolo583f17d2020-02-13 10:35:17 -0800185 "Err": err,
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700186 }).Fatal("Cannot find ONU")
187 }
188
Matteo Scandolo583f17d2020-02-13 10:35:17 -0800189 // creating and storing ONU IDs
190 id := o.LastUsedOnuId[onuDiscInd.IntfId] + 1
191 o.LastUsedOnuId[onuDiscInd.IntfId] = o.LastUsedOnuId[onuDiscInd.IntfId] + 1
192 onu.SetID(id)
193
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700194 var pir uint32 = 1000000
195 Onu := openolt.Onu{
196 IntfId: onu.PonPortID,
Matteo Scandolo583f17d2020-02-13 10:35:17 -0800197 OnuId: id,
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700198 SerialNumber: onu.SerialNumber,
199 Pir: pir,
200 }
201
202 if _, err := client.ActivateOnu(context.Background(), &Onu); err != nil {
203 log.WithFields(log.Fields{
204 "IntfId": onuDiscInd.IntfId,
205 "SerialNumber": common.OnuSnToString(onuDiscInd.SerialNumber),
206 }).Error("Failed to activate ONU")
207 }
208}
209
210func (o *OltMock) handleOnuIndication(client openolt.OpenoltClient, onuInd *openolt.OnuIndication) {
211 log.WithFields(log.Fields{
212 "IntfId": onuInd.IntfId,
213 "SerialNumber": common.OnuSnToString(onuInd.SerialNumber),
214 }).Info("Received Onu indication")
215
216 onu, err := o.Olt.FindOnuBySn(common.OnuSnToString(onuInd.SerialNumber))
217
218 if err != nil {
219 log.WithFields(log.Fields{
220 "IntfId": onuInd.IntfId,
221 "SerialNumber": common.OnuSnToString(onuInd.SerialNumber),
222 }).Fatal("Cannot find ONU")
223 }
224
David Bainbridge103cf022019-12-16 20:11:35 +0000225 ctx, cancel := context.WithCancel(context.TODO())
Matteo Scandolo9ddb3a92021-04-14 16:16:20 -0700226 // NOTE we need to create a fake stream for ProcessOnuMessages
227 // as it listen on the context to cancel the loop
228 // In the BBR case it's not used for anything else
229 mockStream := MockStream{}
230 go onu.ProcessOnuMessages(ctx, &mockStream, client)
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700231
232 go func() {
233
234 defer func() {
235 log.WithFields(log.Fields{
236 "onuSn": common.OnuSnToString(onuInd.SerialNumber),
237 "CompletedOnus": o.CompletedOnus,
238 "TargetOnus": o.TargetOnus,
239 }).Debugf("Onu done")
240
Matteo Scandolo569e7172019-12-20 11:51:51 -0800241 // close the ONU channel
242 cancel()
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700243 }()
244
245 for message := range onu.DoneChannel {
Shrey Baid688b4242020-07-10 20:40:10 +0530246 if message {
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700247 o.CompletedOnus++
248 if o.CompletedOnus == o.TargetOnus {
249 // NOTE once all the ONUs are completed, exit
250 // closing the connection is not the most elegant way,
251 // but I haven't found any other way to stop
252 // the indications.Recv() infinite loop
253 log.Info("Simulation Done")
254 ValidateAndClose(o)
255 }
256
257 break
258 }
259 }
260
261 }()
262
263 // TODO change the state instead of calling an ONU method from here
264 onu.StartOmci(client)
265}
266
267func (o *OltMock) handleOmciIndication(client openolt.OpenoltClient, omciInd *openolt.OmciIndication) {
268
269 pon, err := o.Olt.GetPonById(omciInd.IntfId)
270 if err != nil {
271 log.WithFields(log.Fields{
272 "OnuId": omciInd.OnuId,
273 "IntfId": omciInd.IntfId,
274 "err": err,
275 }).Fatal("Can't find PonPort")
276 }
277 onu, _ := pon.GetOnuById(omciInd.OnuId)
278 if err != nil {
279 log.WithFields(log.Fields{
280 "OnuId": omciInd.OnuId,
281 "IntfId": omciInd.IntfId,
282 "err": err,
283 }).Fatal("Can't find Onu")
284 }
285
286 log.WithFields(log.Fields{
287 "IntfId": onu.PonPortID,
288 "OnuId": onu.ID,
289 "OnuSn": onu.Sn(),
290 "Pkt": omciInd.Pkt,
291 }).Trace("Received Onu omci indication")
292
Matteo Scandolof9d43412021-01-12 11:11:34 -0800293 msg := types.Message{
294 Type: types.OmciIndication,
295 Data: types.OmciIndicationMessage{
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700296 OnuSN: onu.SerialNumber,
297 OnuID: onu.ID,
298 OmciInd: omciInd,
299 },
300 }
301 onu.Channel <- msg
302}
303
304func (o *OltMock) handlePktIndication(client openolt.OpenoltClient, pktIndication *openolt.PacketIndication) {
305
306 pkt := gopacket.NewPacket(pktIndication.Pkt, layers.LayerTypeEthernet, gopacket.Default)
307
Matteo Scandolo618a6582020-09-09 12:21:29 -0700308 pktType, err := packetHandlers.GetPktType(pkt)
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700309
310 if err != nil {
311 log.Warnf("Ignoring packet as it's neither EAPOL or DHCP")
312 return
313 }
314
315 log.WithFields(log.Fields{
316 "IntfType": pktIndication.IntfType,
317 "IntfId": pktIndication.IntfId,
318 "GemportId": pktIndication.GemportId,
319 "FlowId": pktIndication.FlowId,
320 "PortNo": pktIndication.PortNo,
321 "Cookie": pktIndication.Cookie,
322 "pktType": pktType,
323 }).Trace("Received PktIndication")
324
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700325 if pktIndication.IntfType == "nni" {
326 // 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 -0700327
Matteo Scandolo4a036262020-08-17 15:56:13 -0700328 onuMac, err := packetHandlers.GetDstMacAddressFromPacket(pkt)
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700329
330 if err != nil {
331 log.WithFields(log.Fields{
Matteo Scandolo4a036262020-08-17 15:56:13 -0700332 "IntfType": "nni",
333 "Pkt": hex.EncodeToString(pkt.Data()),
334 }).Fatal("Can't find Dst MacAddress in packet")
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700335 }
336
Matteo Scandolo4a036262020-08-17 15:56:13 -0700337 s, err := o.Olt.FindServiceByMacAddress(onuMac)
338 if err != nil {
339 log.WithFields(log.Fields{
340 "IntfType": "nni",
341 "Pkt": hex.EncodeToString(pkt.Data()),
342 "MacAddress": onuMac.String(),
343 }).Fatal("Can't find ONU with MacAddress")
344 }
345
346 service := s.(*devices.Service)
347 onu := service.Onu
348
Matteo Scandolof9d43412021-01-12 11:11:34 -0800349 msg := types.Message{
350 Type: types.OnuPacketIn,
351 Data: types.OnuPacketMessage{
Matteo Scandolo4a036262020-08-17 15:56:13 -0700352 IntfId: pktIndication.IntfId,
353 OnuId: onu.ID,
354 Packet: pkt,
355 Type: pktType,
356 GemPortId: pktIndication.GemportId,
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700357 },
358 }
359 // NOTE we send it on the ONU channel so that is handled as all the others packets in a separate thread
360 onu.Channel <- msg
361 } else {
362 // TODO a very similar construct is used in many places,
363 // abstract this in an OLT method
364 pon, err := o.Olt.GetPonById(pktIndication.IntfId)
365 if err != nil {
366 log.WithFields(log.Fields{
367 "OnuId": pktIndication.PortNo,
368 "IntfId": pktIndication.IntfId,
369 "err": err,
370 }).Fatal("Can't find PonPort")
371 }
372 onu, err := pon.GetOnuById(pktIndication.PortNo)
373 if err != nil {
374 log.WithFields(log.Fields{
375 "OnuId": pktIndication.PortNo,
376 "IntfId": pktIndication.IntfId,
377 "err": err,
378 }).Fatal("Can't find Onu")
379 }
380 // NOTE when we push the EAPOL flow we set the PortNo = OnuId for convenience sake
381 // BBsim responds setting the port number that was sent with the flow
Matteo Scandolof9d43412021-01-12 11:11:34 -0800382 msg := types.Message{
383 Type: types.OnuPacketIn,
384 Data: types.OnuPacketMessage{
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700385 IntfId: pktIndication.IntfId,
386 OnuId: pktIndication.PortNo,
387 Packet: pkt,
388 Type: pktType,
389 },
390 }
391 onu.Channel <- msg
392 }
393}
394
395// TODO Move in a different file
396func Connect(ip string, port string) (openolt.OpenoltClient, *grpc.ClientConn) {
397 server := fmt.Sprintf("%s:%s", ip, port)
398 conn, err := grpc.Dial(server, grpc.WithInsecure())
399
400 if err != nil {
401 log.Fatalf("did not connect: %v", err)
402 return nil, conn
403 }
404 return openolt.NewOpenoltClient(conn), conn
405}