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