blob: a12d8c675dec22d3214d74a3a9ad10afc35b5dc7 [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"
21 "errors"
22 "fmt"
23 "github.com/google/gopacket"
24 "github.com/google/gopacket/layers"
25 "github.com/opencord/bbsim/internal/bbsim/devices"
26 "github.com/opencord/bbsim/internal/bbsim/packetHandlers"
27 "github.com/opencord/bbsim/internal/common"
Matteo Scandolo3de9de02019-11-14 13:40:03 -080028 "github.com/opencord/voltha-protos/v2/go/openolt"
Matteo Scandolo40e067f2019-10-16 16:59:41 -070029 log "github.com/sirupsen/logrus"
30 "google.golang.org/grpc"
31 "io"
32 "reflect"
33 "time"
34)
35
36type OltMock struct {
37 Olt *devices.OltDevice
38 BBSimIp string
39 BBSimPort string
40 BBSimApiPort string
41
42 conn *grpc.ClientConn
43
44 TargetOnus int
45 CompletedOnus int // Number of ONUs that have received a DHCPAck
46}
47
48// trigger an enable call and start the same listeners on the gRPC stream that VOLTHA would create
49// this method is blocking
50func (o *OltMock) Start() {
51 log.Info("Starting Mock OLT")
52
53 for _, pon := range o.Olt.Pons {
54 for _, onu := range pon.Onus {
Matteo Scandolod32c3822019-11-26 15:57:46 -070055 if err := onu.InternalState.Event("initialize"); err != nil {
56 log.Fatalf("Error initializing ONU: %v", err)
57 }
Matteo Scandoloba342de2019-10-22 10:41:33 -070058 log.Tracef("Created ONU: %s (%d:%d)", onu.Sn(), onu.STag, onu.CTag)
Matteo Scandolo40e067f2019-10-16 16:59:41 -070059 }
60 }
61
62 client, conn := Connect(o.BBSimIp, o.BBSimPort)
63 o.conn = conn
64 defer conn.Close()
65
66 deviceInfo, err := o.getDeviceInfo(client)
67
68 if err != nil {
69 log.WithFields(log.Fields{
70 "error": err,
71 }).Fatal("Can't read device info")
72 }
73
74 log.WithFields(log.Fields{
75 "Vendor": deviceInfo.Vendor,
76 "Model": deviceInfo.Model,
77 "DeviceSerialNumber": deviceInfo.DeviceSerialNumber,
78 "PonPorts": deviceInfo.PonPorts,
79 }).Info("Retrieved device info")
80
81 o.readIndications(client)
82
83}
84
85func (o *OltMock) getDeviceInfo(client openolt.OpenoltClient) (*openolt.DeviceInfo, error) {
86 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
87 defer cancel()
88
89 return client.GetDeviceInfo(ctx, new(openolt.Empty))
90}
91
92func (o *OltMock) getOnuByTags(sTag int, cTag int) (*devices.Onu, error) {
93
94 for _, pon := range o.Olt.Pons {
95 for _, onu := range pon.Onus {
96 if onu.STag == sTag && onu.CTag == cTag {
97 return onu, nil
98 }
99 }
100 }
101
102 return nil, errors.New("cant-find-onu-by-c-s-tags")
103}
104
105func (o *OltMock) readIndications(client openolt.OpenoltClient) {
106 defer func() {
107 log.Info("OLT readIndications done")
108 }()
109
110 // Tell the OLT to start sending indications
111 indications, err := client.EnableIndication(context.Background(), new(openolt.Empty))
112 if err != nil {
113 log.WithFields(log.Fields{
114 "error": err,
115 }).Error("Failed to enable indication stream")
116 return
117 }
118
119 // listen for indications
120 for {
121 indication, err := indications.Recv()
122 if err == io.EOF {
123 break
124 }
125 if err != nil {
126
127 // the connection is closed once we have sent the DHCP_ACK packet to all of the ONUs
128 // it means BBR completed, it's not an error
129
130 log.WithFields(log.Fields{
131 "error": err,
132 }).Debug("Failed to read from indications")
133 break
134 }
135
136 o.handleIndication(client, indication)
137 }
138}
139
140func (o *OltMock) handleIndication(client openolt.OpenoltClient, indication *openolt.Indication) {
141 switch indication.Data.(type) {
142 case *openolt.Indication_OltInd:
143 log.Info("Received Indication_OltInd")
144 case *openolt.Indication_IntfInd:
145 log.Info("Received Indication_IntfInd")
146 case *openolt.Indication_IntfOperInd:
147 log.Info("Received Indication_IntfOperInd")
148 case *openolt.Indication_OnuDiscInd:
149 onuDiscInd := indication.GetOnuDiscInd()
150 o.handleOnuDiscIndication(client, onuDiscInd)
151 case *openolt.Indication_OnuInd:
152 onuInd := indication.GetOnuInd()
153 o.handleOnuIndication(client, onuInd)
154 case *openolt.Indication_OmciInd:
155 omciIndication := indication.GetOmciInd()
156 o.handleOmciIndication(client, omciIndication)
157 case *openolt.Indication_PktInd:
158 pktIndication := indication.GetPktInd()
159 o.handlePktIndication(client, pktIndication)
160 case *openolt.Indication_PortStats:
161 case *openolt.Indication_FlowStats:
162 case *openolt.Indication_AlarmInd:
163 default:
164 log.WithFields(log.Fields{
165 "data": indication.Data,
166 "type": reflect.TypeOf(indication.Data),
167 }).Warn("Indication unsupported")
168 }
169}
170
171func (o *OltMock) handleOnuDiscIndication(client openolt.OpenoltClient, onuDiscInd *openolt.OnuDiscIndication) {
172 log.WithFields(log.Fields{
173 "IntfId": onuDiscInd.IntfId,
174 "SerialNumber": common.OnuSnToString(onuDiscInd.SerialNumber),
175 }).Info("Received Onu discovery indication")
176
177 onu, err := o.Olt.FindOnuBySn(common.OnuSnToString(onuDiscInd.SerialNumber))
178
179 if err != nil {
180 log.WithFields(log.Fields{
181 "IntfId": onuDiscInd.IntfId,
182 "SerialNumber": common.OnuSnToString(onuDiscInd.SerialNumber),
183 }).Fatal("Cannot find ONU")
184 }
185
186 var pir uint32 = 1000000
187 Onu := openolt.Onu{
188 IntfId: onu.PonPortID,
189 OnuId: onu.ID,
190 SerialNumber: onu.SerialNumber,
191 Pir: pir,
192 }
193
194 if _, err := client.ActivateOnu(context.Background(), &Onu); err != nil {
195 log.WithFields(log.Fields{
196 "IntfId": onuDiscInd.IntfId,
197 "SerialNumber": common.OnuSnToString(onuDiscInd.SerialNumber),
198 }).Error("Failed to activate ONU")
199 }
200}
201
202func (o *OltMock) handleOnuIndication(client openolt.OpenoltClient, onuInd *openolt.OnuIndication) {
203 log.WithFields(log.Fields{
204 "IntfId": onuInd.IntfId,
205 "SerialNumber": common.OnuSnToString(onuInd.SerialNumber),
206 }).Info("Received Onu indication")
207
208 onu, err := o.Olt.FindOnuBySn(common.OnuSnToString(onuInd.SerialNumber))
209
210 if err != nil {
211 log.WithFields(log.Fields{
212 "IntfId": onuInd.IntfId,
213 "SerialNumber": common.OnuSnToString(onuInd.SerialNumber),
214 }).Fatal("Cannot find ONU")
215 }
216
David Bainbridge103cf022019-12-16 20:11:35 +0000217 ctx, cancel := context.WithCancel(context.TODO())
218 go onu.ProcessOnuMessages(ctx, nil, client)
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700219
220 go func() {
221
222 defer func() {
223 log.WithFields(log.Fields{
224 "onuSn": common.OnuSnToString(onuInd.SerialNumber),
225 "CompletedOnus": o.CompletedOnus,
226 "TargetOnus": o.TargetOnus,
227 }).Debugf("Onu done")
228
Matteo Scandolo569e7172019-12-20 11:51:51 -0800229 // close the ONU channel
230 cancel()
Matteo Scandolo40e067f2019-10-16 16:59:41 -0700231 }()
232
233 for message := range onu.DoneChannel {
234 if message == true {
235 o.CompletedOnus++
236 if o.CompletedOnus == o.TargetOnus {
237 // NOTE once all the ONUs are completed, exit
238 // closing the connection is not the most elegant way,
239 // but I haven't found any other way to stop
240 // the indications.Recv() infinite loop
241 log.Info("Simulation Done")
242 ValidateAndClose(o)
243 }
244
245 break
246 }
247 }
248
249 }()
250
251 // TODO change the state instead of calling an ONU method from here
252 onu.StartOmci(client)
253}
254
255func (o *OltMock) handleOmciIndication(client openolt.OpenoltClient, omciInd *openolt.OmciIndication) {
256
257 pon, err := o.Olt.GetPonById(omciInd.IntfId)
258 if err != nil {
259 log.WithFields(log.Fields{
260 "OnuId": omciInd.OnuId,
261 "IntfId": omciInd.IntfId,
262 "err": err,
263 }).Fatal("Can't find PonPort")
264 }
265 onu, _ := pon.GetOnuById(omciInd.OnuId)
266 if err != nil {
267 log.WithFields(log.Fields{
268 "OnuId": omciInd.OnuId,
269 "IntfId": omciInd.IntfId,
270 "err": err,
271 }).Fatal("Can't find Onu")
272 }
273
274 log.WithFields(log.Fields{
275 "IntfId": onu.PonPortID,
276 "OnuId": onu.ID,
277 "OnuSn": onu.Sn(),
278 "Pkt": omciInd.Pkt,
279 }).Trace("Received Onu omci indication")
280
281 msg := devices.Message{
282 Type: devices.OmciIndication,
283 Data: devices.OmciIndicationMessage{
284 OnuSN: onu.SerialNumber,
285 OnuID: onu.ID,
286 OmciInd: omciInd,
287 },
288 }
289 onu.Channel <- msg
290}
291
292func (o *OltMock) handlePktIndication(client openolt.OpenoltClient, pktIndication *openolt.PacketIndication) {
293
294 pkt := gopacket.NewPacket(pktIndication.Pkt, layers.LayerTypeEthernet, gopacket.Default)
295
296 pktType, err := packetHandlers.IsEapolOrDhcp(pkt)
297
298 if err != nil {
299 log.Warnf("Ignoring packet as it's neither EAPOL or DHCP")
300 return
301 }
302
303 log.WithFields(log.Fields{
304 "IntfType": pktIndication.IntfType,
305 "IntfId": pktIndication.IntfId,
306 "GemportId": pktIndication.GemportId,
307 "FlowId": pktIndication.FlowId,
308 "PortNo": pktIndication.PortNo,
309 "Cookie": pktIndication.Cookie,
310 "pktType": pktType,
311 }).Trace("Received PktIndication")
312
313 msg := devices.Message{}
314 if pktIndication.IntfType == "nni" {
315 // This is an packet that is arriving from the NNI and needs to be sent to an ONU
316 // in this case we need to fin the ONU from the C/S tags
317 // TODO: handle errors in the untagging process
318 sTag, _ := packetHandlers.GetVlanTag(pkt)
319 singleTagPkt, _ := packetHandlers.PopSingleTag(pkt)
320 cTag, _ := packetHandlers.GetVlanTag(singleTagPkt)
321
322 onu, err := o.getOnuByTags(int(sTag), int(cTag))
323
324 if err != nil {
325 log.WithFields(log.Fields{
326 "sTag": sTag,
327 "cTag": cTag,
328 }).Fatalf("Can't find ONU from c/s tags")
329 }
330
331 msg = devices.Message{
332 Type: devices.OnuPacketIn,
333 Data: devices.OnuPacketMessage{
334 IntfId: pktIndication.IntfId,
335 OnuId: onu.ID,
336 Packet: pkt,
337 Type: pktType,
338 },
339 }
340 // NOTE we send it on the ONU channel so that is handled as all the others packets in a separate thread
341 onu.Channel <- msg
342 } else {
343 // TODO a very similar construct is used in many places,
344 // abstract this in an OLT method
345 pon, err := o.Olt.GetPonById(pktIndication.IntfId)
346 if err != nil {
347 log.WithFields(log.Fields{
348 "OnuId": pktIndication.PortNo,
349 "IntfId": pktIndication.IntfId,
350 "err": err,
351 }).Fatal("Can't find PonPort")
352 }
353 onu, err := pon.GetOnuById(pktIndication.PortNo)
354 if err != nil {
355 log.WithFields(log.Fields{
356 "OnuId": pktIndication.PortNo,
357 "IntfId": pktIndication.IntfId,
358 "err": err,
359 }).Fatal("Can't find Onu")
360 }
361 // NOTE when we push the EAPOL flow we set the PortNo = OnuId for convenience sake
362 // BBsim responds setting the port number that was sent with the flow
363 msg = devices.Message{
364 Type: devices.OnuPacketIn,
365 Data: devices.OnuPacketMessage{
366 IntfId: pktIndication.IntfId,
367 OnuId: pktIndication.PortNo,
368 Packet: pkt,
369 Type: pktType,
370 },
371 }
372 onu.Channel <- msg
373 }
374}
375
376// TODO Move in a different file
377func Connect(ip string, port string) (openolt.OpenoltClient, *grpc.ClientConn) {
378 server := fmt.Sprintf("%s:%s", ip, port)
379 conn, err := grpc.Dial(server, grpc.WithInsecure())
380
381 if err != nil {
382 log.Fatalf("did not connect: %v", err)
383 return nil, conn
384 }
385 return openolt.NewOpenoltClient(conn), conn
386}