blob: a6a4a1f0247d3019786c47c6b201395962812e49 [file] [log] [blame]
/*
* Copyright 2018-2024 Open Networking Foundation (ONF) and the ONF Contributors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package openflow
import (
"context"
"fmt"
"math"
"testing"
"github.com/opencord/goloxi"
"github.com/opencord/goloxi/of13"
"github.com/opencord/ofagent-go/internal/pkg/holder"
"github.com/opencord/ofagent-go/internal/pkg/mock"
"github.com/opencord/voltha-protos/v5/go/openflow_13"
"github.com/opencord/voltha-protos/v5/go/voltha"
"github.com/stretchr/testify/assert"
)
var msgSizeLimit = 64000
func createEapolFlow(id int) openflow_13.OfpFlowStats {
portField := openflow_13.OfpOxmField{
OxmClass: openflow_13.OfpOxmClass_OFPXMC_OPENFLOW_BASIC,
Field: &openflow_13.OfpOxmField_OfbField{
OfbField: &openflow_13.OfpOxmOfbField{
Type: openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_IN_PORT,
Value: &openflow_13.OfpOxmOfbField_Port{Port: 16},
},
},
}
ethField := openflow_13.OfpOxmField{
OxmClass: openflow_13.OfpOxmClass_OFPXMC_OPENFLOW_BASIC,
Field: &openflow_13.OfpOxmField_OfbField{
OfbField: &openflow_13.OfpOxmOfbField{
Type: openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_ETH_TYPE,
Value: &openflow_13.OfpOxmOfbField_EthType{EthType: 2048},
},
},
}
vlanField := openflow_13.OfpOxmField{
OxmClass: openflow_13.OfpOxmClass_OFPXMC_OPENFLOW_BASIC,
Field: &openflow_13.OfpOxmField_OfbField{
OfbField: &openflow_13.OfpOxmOfbField{
Type: openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_VID,
Value: &openflow_13.OfpOxmOfbField_VlanVid{VlanVid: 4096},
},
},
}
return openflow_13.OfpFlowStats{
Id: uint64(id),
Priority: 10000,
Flags: 1,
Match: &openflow_13.OfpMatch{
Type: openflow_13.OfpMatchType_OFPMT_OXM,
OxmFields: []*openflow_13.OfpOxmField{
&portField, &ethField, &vlanField,
},
},
Instructions: []*openflow_13.OfpInstruction{
{
Type: uint32(openflow_13.OfpInstructionType_OFPIT_APPLY_ACTIONS),
Data: &openflow_13.OfpInstruction_Actions{
Actions: &openflow_13.OfpInstructionActions{
Actions: []*openflow_13.OfpAction{
{
Type: openflow_13.OfpActionType_OFPAT_OUTPUT,
Action: &openflow_13.OfpAction_Output{
Output: &openflow_13.OfpActionOutput{
Port: 4294967293,
},
},
},
},
},
},
},
{
Type: uint32(openflow_13.OfpInstructionType_OFPIT_WRITE_METADATA),
Data: &openflow_13.OfpInstruction_WriteMetadata{
WriteMetadata: &openflow_13.OfpInstructionWriteMetadata{
Metadata: 274877906944,
},
},
},
{
Type: uint32(openflow_13.OfpInstructionType_OFPIT_METER),
Data: &openflow_13.OfpInstruction_Meter{
Meter: &openflow_13.OfpInstructionMeter{
MeterId: 2,
},
},
},
},
}
}
func createRandomFlows(count int) []*openflow_13.OfpFlowStats {
var flows []*openflow_13.OfpFlowStats
n := 0
for n < count {
flow := createEapolFlow(n)
flows = append(flows, &flow)
n++
}
return flows
}
func createRandomPorts(count int) []*voltha.LogicalPort {
var ports []*voltha.LogicalPort
n := 0
for n < count {
port := voltha.LogicalPort{
Id: fmt.Sprintf("uni-%d", n),
OfpPort: &openflow_13.OfpPort{
PortNo: uint32(n),
HwAddr: []uint32{8, 0, 0, 0, 0, uint32(n)},
Name: fmt.Sprintf("BBSM-%d", n),
State: uint32(openflow_13.OfpPortState_OFPPS_LIVE),
Curr: 4128,
Advertised: 4128,
Peer: 4128,
CurrSpeed: 32,
MaxSpeed: 32,
Config: 1,
Supported: 1,
},
}
ports = append(ports, &port)
n++
}
return ports
}
func newTestOFConnection(flowsCount int, portsCount int) *OFConnection {
flows := openflow_13.Flows{
Items: createRandomFlows(flowsCount),
}
ports := voltha.LogicalPorts{
Items: createRandomPorts(portsCount),
}
volthaClient := mock.MockVolthaClient{
LogicalDeviceFlows: flows,
LogicalPorts: ports,
}
volthaClientHolder := &holder.VolthaServiceClientHolder{}
volthaClientHolder.Set(volthaClient)
return &OFConnection{
VolthaClient: volthaClientHolder,
flowsChunkSize: ofcFlowsChunkSize,
portsChunkSize: ofcPortsChunkSize,
portsDescChunkSize: ofcPortsDescChunkSize,
}
}
func TestHandleFlowStatsRequestExactlyDivisible(t *testing.T) {
generatedFlowsCount := ofcFlowsChunkSize * 5
testCorrectChunkAndReplyMoreFlag(t, generatedFlowsCount)
}
func TestHandleFlowStatsRequestNotExactlyDivisible(t *testing.T) {
generatedFlowsCount := int(ofcFlowsChunkSize * 5.5)
testCorrectChunkAndReplyMoreFlag(t, generatedFlowsCount)
}
func testCorrectChunkAndReplyMoreFlag(t *testing.T, generatedFlowsCount int) {
ofc := newTestOFConnection(generatedFlowsCount, 0)
request := of13.NewFlowStatsRequest()
replies, err := ofc.handleFlowStatsRequest(context.Background(), request)
assert.Equal(t, err, nil)
// check that the correct number of messages is generated
assert.Equal(t, int(math.Ceil(float64(generatedFlowsCount)/ofcFlowsChunkSize)), len(replies))
n := 1
entriesCount := 0
for _, r := range replies {
json, _ := r.Flags.MarshalJSON()
// check that the ReplyMore flag is correctly set in the messages
if n == len(replies) {
assert.Equal(t, string(json), "{}")
} else {
assert.Equal(t, string(json), "{\"OFPSFReplyMore\": true}")
}
// check the message size
enc := goloxi.NewEncoder()
if err := r.Serialize(enc); err != nil {
t.Fail()
}
bytes := enc.Bytes()
fmt.Println("FlowStats msg size: ", len(bytes))
if len(bytes) > msgSizeLimit {
t.Fatal("Message size is bigger than 64KB")
}
entriesCount += len(r.GetEntries())
n++
}
// make sure all the generate item are included in the responses
assert.Equal(t, generatedFlowsCount, entriesCount)
}
func TestHandlePortStatsRequest(t *testing.T) {
generatedPortsCount := 2560
ofc := newTestOFConnection(0, generatedPortsCount)
request := of13.NewPortStatsRequest()
// request stats for all ports
request.PortNo = 0xffffffff
replies, err := ofc.handlePortStatsRequest(context.Background(), request)
assert.Equal(t, err, nil)
assert.Equal(t, int(math.Ceil(float64(generatedPortsCount)/ofcPortsChunkSize)), len(replies))
n := 1
entriesCount := 0
for _, r := range replies {
json, _ := r.Flags.MarshalJSON()
// check that the ReplyMore flag is correctly set in the messages
if n == len(replies) {
assert.Equal(t, string(json), "{}")
} else {
assert.Equal(t, string(json), "{\"OFPSFReplyMore\": true}")
}
// check the message size
enc := goloxi.NewEncoder()
if err := r.Serialize(enc); err != nil {
t.Fail()
}
bytes := enc.Bytes()
fmt.Println("PortStats msg size: ", len(bytes))
if len(bytes) > msgSizeLimit {
t.Fatal("Message size is bigger than 64KB")
}
entriesCount += len(r.GetEntries())
n++
}
// make sure all the generate item are included in the responses
assert.Equal(t, generatedPortsCount, entriesCount)
}
func TestHandlePortDescStatsRequest(t *testing.T) {
generatedPortsCount := 2560
ofc := newTestOFConnection(0, generatedPortsCount)
request := of13.NewPortDescStatsRequest()
replies, err := ofc.handlePortDescStatsRequest(context.Background(), request)
assert.Equal(t, err, nil)
// check that the correct number of messages is generated
assert.Equal(t, int(math.Ceil(float64(generatedPortsCount)/ofcPortsDescChunkSize)), len(replies))
n := 1
entriesCount := 0
for _, r := range replies {
json, _ := r.Flags.MarshalJSON()
// check that the ReplyMore flag is correctly set in the messages
if n == len(replies) {
assert.Equal(t, string(json), "{}")
} else {
assert.Equal(t, string(json), "{\"OFPSFReplyMore\": true}")
}
// check the message size
enc := goloxi.NewEncoder()
if err := r.Serialize(enc); err != nil {
t.Fail()
}
bytes := enc.Bytes()
fmt.Println("PortDesc msg size: ", len(bytes))
if len(bytes) > msgSizeLimit {
t.Fatal("Message size is bigger than 64KB")
}
entriesCount += len(r.GetEntries())
n++
}
// make sure all the generate item are included in the responses
assert.Equal(t, generatedPortsCount, entriesCount)
}