Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move config parsing and validation into config.go #469

Merged
merged 15 commits into from
Feb 10, 2022
Merged
10 changes: 5 additions & 5 deletions conf/upf.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"": "Vdev or sim support. Enable `\"mode\": \"af_xdp\"` to enable AF_XDP mode, or `\"mode\": \"af_packet\"` to enable AF_PACKET mode, or `\"mode\": \"sim\"` to generate synthetic traffic from BESS's Source module",
"": "Vdev or sim support. Enable `\"mode\": \"af_xdp\"` to enable AF_XDP mode, or `\"mode\": \"af_packet\"` to enable AF_PACKET mode, `\"mode\": \"sim\"` to generate synthetic traffic from BESS's Source module or \"mode\": \"\" when running with UP4",
"": "mode: af_xdp",
"": "mode: af_packet",
"": "mode: sim",
"mode": "dpdk",

"table_sizes": {
"": "Example sizes based on sim mode and 50K sessions. Customize as per your control plane",
Expand Down Expand Up @@ -145,9 +146,8 @@

"": "p4rtc interface settings",
"p4rtciface": {
"access_ip": "172.17.0.1/32",
"p4rtc_server": "onos",
"p4rtc_port": "51001",
"ue_ip_pool": "10.250.0.0/24"
"access_ip": "172.17.0.1/32",
"p4rtc_server": "onos",
"p4rtc_port": "51001"
}
}
200 changes: 200 additions & 0 deletions pfcpiface/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022-present Open Networking Foundation

package main

import (
log "github.com/sirupsen/logrus"

"net"
"time"

"encoding/json"
"io/ioutil"
"os"
)

// Conf : Json conf struct.
type Conf struct {
Mode string `json:"mode"`
AccessIface IfaceType `json:"access"`
CoreIface IfaceType `json:"core"`
CPIface CPIfaceInfo `json:"cpiface"`
P4rtcIface P4rtcInfo `json:"p4rtciface"`
EnableP4rt bool `json:"enable_p4rt"`
EnableFlowMeasure bool `json:"measure_flow"`
SimInfo SimModeInfo `json:"sim"`
ConnTimeout uint32 `json:"conn_timeout"` // TODO(max): unused, remove
ReadTimeout uint32 `json:"read_timeout"` // TODO(max): convert to duration string
EnableNotifyBess bool `json:"enable_notify_bess"`
EnableEndMarker bool `json:"enable_end_marker"`
NotifySockAddr string `json:"notify_sockaddr"`
EndMarkerSockAddr string `json:"endmarker_sockaddr"`
LogLevel log.Level `json:"log_level"`
QciQosConfig []QciQosConfig `json:"qci_qos_config"`
SliceMeterConfig SliceMeterConfig `json:"slice_rate_limit_config"`
MaxReqRetries uint8 `json:"max_req_retries"`
RespTimeout string `json:"resp_timeout"`
EnableHBTimer bool `json:"enable_hbTimer"`
HeartBeatInterval string `json:"heart_beat_interval"`
}

// QciQosConfig : Qos configured attributes.
type QciQosConfig struct {
QCI uint8 `json:"qci"`
CBS uint32 `json:"cbs"`
PBS uint32 `json:"pbs"`
EBS uint32 `json:"ebs"`
BurstDurationMs uint32 `json:"burst_duration_ms"`
SchedulingPriority uint32 `json:"priority"`
}

type SliceMeterConfig struct {
N6RateBps uint64 `json:"n6_bps"`
N6BurstBytes uint64 `json:"n6_burst_bytes"`
N3RateBps uint64 `json:"n3_bps"`
N3BurstBytes uint64 `json:"n3_burst_bytes"`
}

// SimModeInfo : Sim mode attributes.
type SimModeInfo struct {
MaxSessions uint32 `json:"max_sessions"`
StartUEIP net.IP `json:"start_ue_ip"`
StartENBIP net.IP `json:"start_enb_ip"`
StartAUPFIP net.IP `json:"start_aupf_ip"`
N6AppIP net.IP `json:"n6_app_ip"`
N9AppIP net.IP `json:"n9_app_ip"`
StartN3TEID string `json:"start_n3_teid"`
StartN9TEID string `json:"start_n9_teid"`
}

// CPIfaceInfo : CPIface interface settings.
type CPIfaceInfo struct {
Peers []string `json:"peers"`
UseFQDN bool `json:"use_fqdn"`
NodeID string `json:"hostname"`
HTTPPort string `json:"http_port"`
Dnn string `json:"dnn"`
EnableUeIPAlloc bool `json:"enable_ue_ip_alloc"`
UEIPPool string `json:"ue_ip_pool"`
}

osinstom marked this conversation as resolved.
Show resolved Hide resolved
// IfaceType : Gateway interface struct.
type IfaceType struct {
IfName string `json:"ifname"`
}

// P4rtcInfo : P4 runtime interface settings.
type P4rtcInfo struct {
AccessIP string `json:"access_ip"`
P4rtcServer string `json:"p4rtc_server"`
P4rtcPort string `json:"p4rtc_port"`
}

// validateConf checks that the given config reaches a baseline of correctness.
func validateConf(conf Conf) error {
if conf.EnableP4rt {
_, _, err := net.ParseCIDR(conf.P4rtcIface.AccessIP)
if err != nil {
return ErrInvalidArgumentWithReason("conf.P4rtcIface.AccessIP", conf.P4rtcIface.AccessIP, err.Error())
}

if !conf.CPIface.EnableUeIPAlloc {
return ErrInvalidArgumentWithReason("conf.EnableUeIPAlloc",
conf.CPIface.EnableUeIPAlloc, "UE IP pool allocation must be enabled in P4RT mode")
}

if conf.Mode != "" {
return ErrInvalidArgumentWithReason("conf.Mode", conf.Mode, "mode must not be set for UP4")
}
} else {
// Mode is only relevant in a BESS deployment.
validModes := map[string]struct{}{
"af_xdp": {},
"af_packet": {},
"dpdk": {},
"sim": {},
}
if _, ok := validModes[conf.Mode]; !ok {
return ErrInvalidArgumentWithReason("conf.Mode", conf.Mode, "invalid mode")
}
}

if conf.CPIface.EnableUeIPAlloc {
_, _, err := net.ParseCIDR(conf.CPIface.UEIPPool)
if err != nil {
return ErrInvalidArgumentWithReason("conf.UEIPPool", conf.CPIface.UEIPPool, err.Error())
}
}

for _, peer := range conf.CPIface.Peers {
ip := net.ParseIP(peer)
if ip == nil {
return ErrInvalidArgumentWithReason("conf.CPIface.Peers", peer, "invalid IP")
}
}

if _, err := time.ParseDuration(conf.RespTimeout); err != nil {
return ErrInvalidArgumentWithReason("conf.RespTimeout", conf.RespTimeout, "invalid duration")
}

if conf.ReadTimeout == 0 {
return ErrInvalidArgumentWithReason("conf.ReadTimeout", conf.ReadTimeout, "invalid duration")
}

if conf.MaxReqRetries == 0 {
return ErrInvalidArgumentWithReason("conf.MaxReqRetries", conf.MaxReqRetries, "invalid number of retries")
}

if conf.EnableHBTimer {
if _, err := time.ParseDuration(conf.HeartBeatInterval); err != nil {
return err
}
}

return nil
}

// LoadConfigFile : parse json file and populate corresponding struct.
func LoadConfigFile(filepath string) (Conf, error) {
// Open up file.
jsonFile, err := os.Open(filepath)
if err != nil {
return Conf{}, err
}
defer jsonFile.Close()

// Read our file into memory.
byteValue, err := ioutil.ReadAll(jsonFile)
if err != nil {
return Conf{}, err
}

var conf Conf

err = json.Unmarshal(byteValue, &conf)
if err != nil {
return Conf{}, err
}

// Set defaults, when missing.
if conf.RespTimeout == "" {
conf.RespTimeout = respTimeoutDefault.String()
}

if conf.ReadTimeout == 0 {
conf.ReadTimeout = uint32(readTimeoutDefault.Seconds())
}

if conf.MaxReqRetries == 0 {
conf.MaxReqRetries = maxReqRetriesDefault
}

// Perform basic validation.
err = validateConf(conf)
if err != nil {
return Conf{}, err
}

return conf, nil
}
83 changes: 83 additions & 0 deletions pfcpiface/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022-present Open Networking Foundation

package main

import (
"io/fs"
"io/ioutil"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func mustWriteStringToDisk(s string, path string) {
err := ioutil.WriteFile(path, []byte(s), fs.ModePerm)
if err != nil {
panic(err)
}
}

func TestLoadConfigFile(t *testing.T) {
t.Run("sample config is valid", func(t *testing.T) {
s := `{
pudelkoM marked this conversation as resolved.
Show resolved Hide resolved
"mode": "dpdk",
"log_level": "info",
"workers": 1,
"max_sessions": 50000,
"table_sizes": {
"pdrLookup": 50000,
"appQERLookup": 200000,
"sessionQERLookup": 100000,
"farLookup": 150000
},
"access": {
"ifname": "access"
},
"core": {
"ifname": "core"
},
"measure_upf": true,
"measure_flow": true,
"enable_notify_bess": true,
"notify_sockaddr": "/pod-share/notifycp",
"cpiface": {
"dnn": "internet",
"hostname": "upf",
"http_port": "8080"
},
"n6_bps": 1000000000,
"n6_burst_bytes": 12500000,
"n3_bps": 1000000000,
"n3_burst_bytes": 12500000,
"qci_qos_config": [{
"qci": 0,
"cbs": 50000,
"ebs": 50000,
"pbs": 50000,
"burst_duration_ms": 10,
"priority": 7
}]
}`
confPath := t.TempDir() + "/conf.json"
mustWriteStringToDisk(s, confPath)

_, err := LoadConfigFile(confPath)
require.NoError(t, err)
})

t.Run("all sample configs must be valid", func(t *testing.T) {
paths := []string{
"../conf/upf.json",
"../ptf/config/upf.json",
"../test/integration/config/default.json",
"../test/integration/config/ue_ip_alloc.json",
}

for _, path := range paths {
_, err := LoadConfigFile(path)
assert.NoError(t, err, "config %v is not valid", path)
}
})
}