[VOL-4627] Add COMBO pon support with configuration file
Change-Id: Ib7eec5640dfd5e5eb39e722ec7d2c8ee2b59f060
diff --git a/internal/common/omci/onu_mib_db.go b/internal/common/omci/onu_mib_db.go
index e9fabed..2902fd2 100644
--- a/internal/common/omci/onu_mib_db.go
+++ b/internal/common/omci/onu_mib_db.go
@@ -21,6 +21,7 @@
"encoding/binary"
"encoding/hex"
+ "github.com/opencord/bbsim/internal/common"
me "github.com/opencord/omci-lib-go/v2/generated"
)
@@ -70,6 +71,7 @@
cardHolderOnuType byte = 0x01 // ONU is a single piece of integrated equipment
ethernetUnitType byte = 0x2f // Ethernet BASE-T
xgsPonUnitType byte = 0xee // XG-PON10G10
+ gPonUnitType byte = 0xf5 // GPON12441244
potsUnitType byte = 0x20 // POTS
cardHolderSlotID byte = 0x01
tcontSlotId byte = 0x80 // why is this not the same as the cardHolderSlotID, it does not point to anything
@@ -92,7 +94,7 @@
// creates a MIB database for a ONU
// CircuitPack and CardHolder are static, everything else can be configured
-func GenerateMibDatabase(ethUniPortCount int, potsUniPortCount int) (*MibDb, error) {
+func GenerateMibDatabase(ethUniPortCount int, potsUniPortCount int, technology common.PonTechnology) (*MibDb, error) {
mibDb := MibDb{
items: []MibDbEntry{},
@@ -116,12 +118,21 @@
// },
//})
- // circuitPack XG-PON10G10
+ // ANI circuitPack
+ var aniCPType byte
+
+ switch technology {
+ case common.XGSPON:
+ aniCPType = xgsPonUnitType
+ case common.GPON:
+ aniCPType = gPonUnitType
+ }
+
mibDb.items = append(mibDb.items, MibDbEntry{
me.CircuitPackClassID,
circuitPackEntityID,
me.AttributeValueMap{
- me.CircuitPack_Type: xgsPonUnitType,
+ me.CircuitPack_Type: aniCPType,
me.CircuitPack_NumberOfPorts: 1, // NOTE is this the ANI port? must be
me.CircuitPack_SerialNumber: ToOctets("BBSM-Circuit-Pack-ani", 20),
me.CircuitPack_Version: ToOctets("v0.0.1", 20),
diff --git a/internal/common/omci/onu_mib_db_test.go b/internal/common/omci/onu_mib_db_test.go
index 378ac6e..2794aa4 100644
--- a/internal/common/omci/onu_mib_db_test.go
+++ b/internal/common/omci/onu_mib_db_test.go
@@ -19,6 +19,7 @@
import (
"testing"
+ "github.com/opencord/bbsim/internal/common"
"github.com/opencord/omci-lib-go/v2"
me "github.com/opencord/omci-lib-go/v2/generated"
"github.com/stretchr/testify/assert"
@@ -56,7 +57,7 @@
func Test_GenerateMibDatabase(t *testing.T) {
const uniPortCount = 4
- mibDb, err := GenerateMibDatabase(uniPortCount, 0)
+ mibDb, err := GenerateMibDatabase(uniPortCount, 0, common.XGSPON)
expectedItems := 9 //ONU-G + 2 Circuit Packs (4 messages each)
expectedItems += 2 * uniPortCount // 1 PPTP and 1 UniG per UNI
@@ -94,7 +95,7 @@
func Test_GenerateMibDatabase_withPots(t *testing.T) {
const uniPortCount = 4
const potsPortCount = 1
- mibDb, err := GenerateMibDatabase(uniPortCount, potsPortCount)
+ mibDb, err := GenerateMibDatabase(uniPortCount, potsPortCount, common.XGSPON)
expectedItems := 13 //ONU-G + 3 Circuit Packs (4 messages each)
expectedItems += 2 * (uniPortCount + potsPortCount) // 1 PPTP and 1 UniG per UNI
diff --git a/internal/common/option_test.go b/internal/common/option_test.go
index 2a57624..bf8d139 100644
--- a/internal/common/option_test.go
+++ b/internal/common/option_test.go
@@ -17,8 +17,11 @@
package common
import (
- "gotest.tools/assert"
+ "fmt"
"testing"
+
+ "github.com/imdario/mergo"
+ "gotest.tools/assert"
)
func TestLoadBBSimServices(t *testing.T) {
@@ -36,3 +39,186 @@
assert.Equal(t, services[0].NeedsIgmp, false)
assert.Equal(t, services[0].TechnologyProfileID, 64)
}
+
+func TestLoadPonsConfigDefaults(t *testing.T) {
+ Config = getDefaultOps()
+ // The default options define 1 PON per OLT
+ // and 1 ONU per PON
+
+ Services = []ServiceYaml{
+ {
+ Name: "test",
+ },
+ }
+
+ ponsConf, err := getDefaultPonsConfig()
+
+ assert.NilError(t, err, "Can't get defaults")
+
+ assert.Equal(t, ponsConf.Number, uint32(1))
+ assert.Equal(t, len(ponsConf.Ranges), 1)
+
+ ranges := ponsConf.Ranges
+
+ //The default should replicate the old way bbsim used to compute ranges
+ assert.Equal(t, ranges[0].PonRange, IdRange{0, 0})
+ assert.Equal(t, ranges[0].Technology, XGSPON.String())
+ assert.Equal(t, ranges[0].OnuRange, IdRange{defaultOnuIdStart, defaultOnuIdStart})
+ assert.Equal(t, ranges[0].AllocIdRange, IdRange{defaultAllocIdStart, defaultAllocIdStart + 4})
+ assert.Equal(t, ranges[0].GemportRange, IdRange{defaultGemportIdStart, defaultGemportIdStart + 32})
+
+ assert.NilError(t, validatePonsConfig(ponsConf), "Configuration is not valid")
+}
+
+func TestLoadPonsConfigFile(t *testing.T) {
+
+ Config = getDefaultOps()
+
+ Services = []ServiceYaml{
+ {
+ Name: "test",
+ },
+ }
+
+ ponsConf, err := getDefaultPonsConfig()
+
+ assert.NilError(t, err, "Can't get defaults")
+
+ yamlConf, err := loadBBSimPons("../../configs/pon-interfaces.yaml")
+
+ assert.NilError(t, err, "Can't read config file")
+
+ // merging Yaml and Default Values
+ err = mergo.Merge(ponsConf, yamlConf, mergo.WithOverride)
+ assert.NilError(t, err, "Can't merge YAML and Config")
+
+ assert.Equal(t, ponsConf.Number, uint32(16))
+ assert.Equal(t, len(ponsConf.Ranges), 1)
+
+ ranges := ponsConf.Ranges
+
+ assert.Equal(t, ranges[0].PonRange, IdRange{0, 15})
+ assert.Equal(t, ranges[0].Technology, XGSPON.String())
+ assert.Equal(t, ranges[0].OnuRange, IdRange{1, 255})
+ assert.Equal(t, ranges[0].AllocIdRange, IdRange{256, 1024})
+ assert.Equal(t, ranges[0].GemportRange, IdRange{256, 1024})
+
+ assert.NilError(t, validatePonsConfig(ponsConf), "Configuration is not valid")
+}
+
+func getTestPonsConfiguration() *PonPortsConfig {
+ return &PonPortsConfig{
+ Number: 16,
+ Ranges: []PonRangeConfig{
+ {
+ PonRange: IdRange{0, 7},
+ Technology: GPON.String(),
+ OnuRange: IdRange{defaultOnuIdStart, defaultOnuIdStart},
+ AllocIdRange: IdRange{defaultAllocIdStart, defaultAllocIdStart + 4},
+ GemportRange: IdRange{defaultGemportIdStart, defaultGemportIdStart + 32},
+ },
+ {
+ PonRange: IdRange{8, 15},
+ Technology: XGSPON.String(),
+ OnuRange: IdRange{defaultOnuIdStart, defaultOnuIdStart},
+ AllocIdRange: IdRange{defaultAllocIdStart, defaultAllocIdStart + 4},
+ GemportRange: IdRange{defaultGemportIdStart, defaultGemportIdStart + 32},
+ },
+ },
+ }
+}
+
+func TestPonsValidationTechnology(t *testing.T) {
+ ponsConf := getTestPonsConfiguration()
+ assert.NilError(t, validatePonsConfig(ponsConf), "Test configuration is not valid")
+
+ ponsConf.Ranges[0].Technology = XGSPON.String()
+ assert.NilError(t, validatePonsConfig(ponsConf), "Correct technology considered invalid")
+
+ ponsConf.Ranges[0].Technology = GPON.String()
+ assert.NilError(t, validatePonsConfig(ponsConf), "Correct technology considered invalid")
+
+ ponsConf.Ranges[0].Technology = "TEST"
+ assert.ErrorContains(t, validatePonsConfig(ponsConf), "technology", "Incorrect technology considered valid")
+}
+
+func TestPonsValidationPortsInRanges(t *testing.T) {
+ ponsConf := getTestPonsConfiguration()
+ assert.NilError(t, validatePonsConfig(ponsConf), "Test configuration is not valid")
+
+ //The second range now misses pon 8
+ ponsConf.Ranges[1].PonRange.StartId = 9
+
+ assert.ErrorContains(t, validatePonsConfig(ponsConf), "not-defined", "Missing pon definition considered valid")
+
+ //The second range defines pon 7 a second time
+ ponsConf.Ranges[1].PonRange.StartId = 7
+
+ assert.ErrorContains(t, validatePonsConfig(ponsConf), "duplicate", "Duplicate pon definition considered valid")
+
+ //Get back to a known good configuration
+ ponsConf = getTestPonsConfiguration()
+ //The second range uses an Id that is out of range
+ ponsConf.Ranges[1].PonRange.EndId = 16
+
+ assert.ErrorContains(t, validatePonsConfig(ponsConf), "max", "Out of range pon definition considered valid")
+
+ //Fix the start of the second range
+ ponsConf.Ranges[1].PonRange.EndId = 15
+
+ ponsConf.Number = 0
+
+ assert.ErrorContains(t, validatePonsConfig(ponsConf), "no-pon-ports", "Zero pons considered valid")
+}
+
+func TestPonsValidationRangeLimits(t *testing.T) {
+ ponsConf := getTestPonsConfiguration()
+ assert.NilError(t, validatePonsConfig(ponsConf), "Test configuration is not valid")
+
+ ponsConf.Ranges[0].PonRange = IdRange{0, 0}
+ ponsConf.Ranges[1].PonRange = IdRange{1, 15}
+
+ assert.NilError(t, validatePonsConfig(ponsConf), "Single pon range considered invalid")
+
+ //Get back to a known good configuration
+ ponsConf = getTestPonsConfiguration()
+ ponsConf.Ranges[0].PonRange = IdRange{5, 4}
+
+ assert.ErrorContains(t, validatePonsConfig(ponsConf), "limits", "Invalid pons range limits considered valid")
+
+ ponsConf = getTestPonsConfiguration()
+ ponsConf.Ranges[0].OnuRange = IdRange{5, 4}
+
+ assert.ErrorContains(t, validatePonsConfig(ponsConf), "limits", "Invalid onus range limits considered valid")
+
+ ponsConf = getTestPonsConfiguration()
+ ponsConf.Ranges[0].AllocIdRange = IdRange{5, 4}
+
+ assert.ErrorContains(t, validatePonsConfig(ponsConf), "limits", "Invalid alloc-ids range limits considered valid")
+
+ ponsConf = getTestPonsConfiguration()
+ ponsConf.Ranges[0].GemportRange = IdRange{5, 4}
+
+ assert.ErrorContains(t, validatePonsConfig(ponsConf), "limits", "Invalid gemports range limits considered valid")
+}
+
+func TestGetPonConfigById(t *testing.T) {
+ PonsConfig = getTestPonsConfiguration()
+
+ for id := uint32(0); id < PonsConfig.Number-1; id++ {
+ conf, err := GetPonConfigById(id)
+ assert.NilError(t, err, fmt.Sprintf("Cannot get configuration for pon %d", id))
+
+ if id > conf.PonRange.EndId || id < conf.PonRange.StartId {
+ assert.NilError(t, err, fmt.Sprintf("Got wrong configuration for pon %d", id))
+ }
+ }
+
+ _, err := GetPonConfigById(16)
+
+ assert.Assert(t, err != nil, "Invalid pon id returned configuration")
+
+ _, err = GetPonConfigById(100)
+
+ assert.Assert(t, err != nil, "Invalid pon id returned configuration")
+}
diff --git a/internal/common/options.go b/internal/common/options.go
index 000a8dc..58cec20 100644
--- a/internal/common/options.go
+++ b/internal/common/options.go
@@ -88,7 +88,6 @@
NniPorts uint32 `yaml:"nni_ports"`
NniSpeed uint32 `yaml:"nni_speed"`
OnusPonPort uint32 `yaml:"onus_per_port"`
- Technology string `yaml:"technology"`
ID int `yaml:"id"`
OltRebootDelay int `yaml:"reboot_delay"`
PortStatsInterval int `yaml:"port_stats_interval"`
@@ -97,9 +96,77 @@
PotsPorts uint32 `yaml:"pots_ports"`
}
+type PonPortsConfig struct {
+ Number uint32 `yaml:"num_pon_ports"`
+ Ranges []PonRangeConfig `yaml:"ranges"`
+}
+
+type IdRange struct {
+ StartId uint32 `yaml:"start"`
+ EndId uint32 `yaml:"end"`
+}
+
+type PonTechnology int
+
+var ponTechnologyValues = []string{
+ "GPON", "XGS-PON",
+}
+
+func (t PonTechnology) String() string {
+ return ponTechnologyValues[t]
+}
+
+const (
+ GPON PonTechnology = iota
+ XGSPON
+)
+
+func PonTechnologyFromString(s string) (PonTechnology, error) {
+ for i, val := range ponTechnologyValues {
+ if val == s {
+ return PonTechnology(i), nil
+ }
+ }
+ log.WithFields(log.Fields{
+ "ValidValues": strings.Join(ponTechnologyValues[:], ", "),
+ }).Errorf("%s-is-not-a-valid-pon-technology", s)
+ return -1, fmt.Errorf("%s-is-not-a-valid-pon-technology", s)
+}
+
+//Constants for default allocation ranges
+const (
+ defaultOnuIdStart = 1
+ defaultAllocIdStart = 1024
+ defaultGemPortIdPerAllocId = 8
+ defaultGemportIdStart = 1024
+)
+
+type PonRangeConfig struct {
+ PonRange IdRange `yaml:"pon_id_range"`
+ Technology string `yaml:"tech"`
+ OnuRange IdRange `yaml:"onu_id_range"`
+ AllocIdRange IdRange `yaml:"alloc_id_range"`
+ GemportRange IdRange `yaml:"gemport_id_range"`
+}
+
+func GetPonConfigById(id uint32) (*PonRangeConfig, error) {
+ if PonsConfig == nil {
+ return nil, fmt.Errorf("pons-config-nil")
+ }
+
+ for _, r := range PonsConfig.Ranges {
+ if id >= r.PonRange.StartId && id <= r.PonRange.EndId {
+ return &r, nil
+ }
+ }
+
+ return nil, fmt.Errorf("pon-config-for-id-%d-not-found", id)
+}
+
type BBSimConfig struct {
ConfigFile string
ServiceConfigFile string
+ PonsConfigFile string
DhcpRetry bool `yaml:"dhcp_retry"`
AuthRetry bool `yaml:"auth_retry"`
LogLevel string `yaml:"log_level"`
@@ -172,8 +239,9 @@
}
var (
- Config *GlobalConfig
- Services []ServiceYaml
+ Config *GlobalConfig
+ Services []ServiceYaml
+ PonsConfig *PonPortsConfig
)
// Load the BBSim configuration. This is a combination of CLI parameters and YAML files
@@ -221,6 +289,31 @@
Services = services
+ //A blank filename means we should fall back to bbsim defaults
+ if Config.BBSim.PonsConfigFile == "" {
+ PonsConfig, err = getDefaultPonsConfig()
+ if err != nil {
+ log.WithFields(log.Fields{
+ "err": err,
+ }).Fatal("Can't load Pon interfaces defaults.")
+ }
+ } else {
+ PonsConfig, err = loadBBSimPons(Config.BBSim.PonsConfigFile)
+
+ if err != nil {
+ log.WithFields(log.Fields{
+ "file": Config.BBSim.PonsConfigFile,
+ "err": err,
+ }).Fatal("Can't read services file")
+ }
+ }
+
+ if err := validatePonsConfig(PonsConfig); err != nil {
+ log.WithFields(log.Fields{
+ "file": Config.BBSim.PonsConfigFile,
+ "err": err,
+ }).Fatal("Invalid Pon interfaces configuration")
+ }
}
func readCliParams() *GlobalConfig {
@@ -229,6 +322,7 @@
configFile := flag.String("config", conf.BBSim.ConfigFile, "Configuration file path")
servicesFile := flag.String("services", conf.BBSim.ServiceConfigFile, "Service Configuration file path")
+ ponsFile := flag.String("pon_port_config_file", conf.BBSim.PonsConfigFile, "Pon Interfaces Configuration file path")
sadisBpFormat := flag.String("bp_format", conf.BBSim.BandwidthProfileFormat, "Bandwidth profile format, 'mef' or 'ietf'")
olt_id := flag.Int("olt_id", conf.Olt.ID, "OLT device ID")
@@ -275,6 +369,7 @@
conf.Olt.OmciResponseRate = uint8(*omci_response_rate)
conf.BBSim.ConfigFile = *configFile
conf.BBSim.ServiceConfigFile = *servicesFile
+ conf.BBSim.PonsConfigFile = *ponsFile
conf.BBSim.CpuProfile = profileCpu
conf.BBSim.LogLevel = *logLevel
conf.BBSim.LogCaller = *logCaller
@@ -309,8 +404,12 @@
c := &GlobalConfig{
BBSimConfig{
- ConfigFile: "configs/bbsim.yaml",
- ServiceConfigFile: "configs/att-services.yaml",
+ ConfigFile: "configs/bbsim.yaml",
+ ServiceConfigFile: "configs/att-services.yaml",
+ // PonsConfigFile is left intentionally blank here
+ // to use the default values computed at runtime depending
+ // on the loaded Services
+ PonsConfigFile: "",
LogLevel: "debug",
LogCaller: false,
Delay: 200,
@@ -341,7 +440,6 @@
NniPorts: 1,
NniSpeed: 10000, //Mbps
OnusPonPort: 1,
- Technology: "XGS-PON",
ID: 0,
OltRebootDelay: 60,
PortStatsInterval: 20,
@@ -357,6 +455,35 @@
return c
}
+func getDefaultPonsConfig() (*PonPortsConfig, error) {
+
+ if Config == nil {
+ return nil, fmt.Errorf("Config is nil")
+ }
+ if Services == nil {
+ return nil, fmt.Errorf("Services is nil")
+ }
+
+ //The default should replicate the old way bbsim used to compute resource ranges based on the configuration
+ // 1 allocId per Service * UNI
+ allocIdPerOnu := uint32(Config.Olt.UniPorts * uint32(len(Services)))
+ return &PonPortsConfig{
+ Number: Config.Olt.PonPorts,
+ Ranges: []PonRangeConfig{
+ {
+ PonRange: IdRange{0, Config.Olt.PonPorts - 1},
+ Technology: XGSPON.String(),
+ // we need one ONU ID available per ONU, but the smaller the range the smaller the pool created in the openolt adapter
+ OnuRange: IdRange{defaultOnuIdStart, defaultOnuIdStart + (Config.Olt.OnusPonPort - 1)},
+ // 1 allocId per Service * UNI * ONU
+ AllocIdRange: IdRange{defaultAllocIdStart, defaultAllocIdStart + (Config.Olt.OnusPonPort * allocIdPerOnu)},
+ // up to 8 gemport-id per tcont/alloc-id
+ GemportRange: IdRange{defaultGemportIdStart, defaultGemportIdStart + Config.Olt.OnusPonPort*allocIdPerOnu*defaultGemPortIdPerAllocId},
+ },
+ },
+ }, nil
+}
+
// LoadBBSimConf loads the BBSim configuration from a YAML file
func loadBBSimConf(filename string) (*GlobalConfig, error) {
yamlConfig := getDefaultOps()
@@ -378,6 +505,84 @@
return yamlConfig, nil
}
+// loadBBSimPons loads the configuration of PON interfaces from a YAML file
+func loadBBSimPons(filename string) (*PonPortsConfig, error) {
+ yamlPonsConfig, err := getDefaultPonsConfig()
+ if err != nil {
+ log.WithFields(log.Fields{
+ "err": err,
+ }).Error("Can't load Pon interfaces defaults.")
+ return nil, err
+ }
+
+ yamlFile, err := ioutil.ReadFile(filename)
+ if err != nil {
+ log.WithFields(log.Fields{
+ "err": err,
+ "filename": filename,
+ }).Error("Cannot load Pon interfaces configuration file. Using defaults.")
+ return yamlPonsConfig, nil
+ }
+
+ err = yaml.Unmarshal(yamlFile, yamlPonsConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return yamlPonsConfig, nil
+}
+
+// validatePonsConfig checks if the configuration to use for the definition of Pon interfaces is valid
+func validatePonsConfig(pons *PonPortsConfig) error {
+
+ if pons.Number == 0 {
+ return fmt.Errorf("no-pon-ports")
+ }
+
+ definedPorts := make([]int, pons.Number)
+
+ for rIndex, resRange := range pons.Ranges {
+ if _, err := PonTechnologyFromString(resRange.Technology); err != nil {
+ return err
+ }
+
+ if resRange.PonRange.EndId < resRange.PonRange.StartId {
+ return fmt.Errorf("invalid-pon-ports-limits-in-range-%d", rIndex)
+ }
+
+ //Keep track of the defined pons
+ for p := resRange.PonRange.StartId; p <= resRange.PonRange.EndId; p++ {
+ if p > uint32(len(definedPorts)-1) {
+ return fmt.Errorf("pon-port-%d-in-range-%d-but-max-is-%d", p, rIndex, pons.Number-1)
+ }
+ definedPorts[p]++
+
+ if definedPorts[p] > 1 {
+ return fmt.Errorf("pon-port-%d-has-duplicate-definition-in-range-%d", p, rIndex)
+ }
+ }
+
+ if resRange.OnuRange.EndId < resRange.OnuRange.StartId {
+ return fmt.Errorf("invalid-onus-limits-in-range-%d", rIndex)
+ }
+ if resRange.AllocIdRange.EndId < resRange.AllocIdRange.StartId {
+ return fmt.Errorf("invalid-allocid-limits-in-range-%d", rIndex)
+ }
+ if resRange.GemportRange.EndId < resRange.GemportRange.StartId {
+ return fmt.Errorf("invalid-gemport-limits-in-range-%d", rIndex)
+ }
+ }
+
+ //Check if the ranges define all the pons
+ for i, num := range definedPorts {
+ if num < 1 {
+ return fmt.Errorf("pon-port-%d-is-not-defined-in-ranges", i)
+ }
+ }
+
+ return nil
+}
+
// LoadBBSimServices parses a file describing the services that need to be created for each UNI
func loadBBSimServices(filename string) ([]ServiceYaml, error) {