[VOL-2778] Introducing Service definition in order to support the TT workflow

Change-Id: Ib171502e8940b5d0b219620a4503f7095d376d7a
diff --git a/internal/common/options.go b/internal/common/options.go
index 5dd1553..567837b 100644
--- a/internal/common/options.go
+++ b/internal/common/options.go
@@ -23,8 +23,9 @@
 	"net"
 	"strings"
 
-	"github.com/ghodss/yaml"
+	"github.com/imdario/mergo"
 	log "github.com/sirupsen/logrus"
+	"gopkg.in/yaml.v2"
 )
 
 var tagAllocationValues = []string{
@@ -41,7 +42,7 @@
 
 func tagAllocationFromString(s string) (TagAllocation, error) {
 	for i, v := range tagAllocationValues {
-		if v == s {
+		if v == strings.TrimSpace(s) {
 			return TagAllocation(i), nil
 		}
 	}
@@ -57,47 +58,15 @@
 	TagAllocationUnique
 )
 
-var sadisFormatValues = []string{
-	"unknown",
-	"att",
-	"dt",
-	"tt",
-}
-
-type SadisFormat int
-
-func (s SadisFormat) String() string {
-	return sadisFormatValues[s]
-}
-
-func sadisFormatFromString(s string) (SadisFormat, error) {
-	for i, v := range sadisFormatValues {
-		if v == s {
-			return SadisFormat(i), nil
-		}
-	}
-	log.WithFields(log.Fields{
-		"ValidValues": strings.Join(sadisFormatValues[1:], ", "),
-	}).Errorf("%s-is-not-a-valid-sadis-format", s)
-	return SadisFormat(0), fmt.Errorf("%s-is-not-a-valid-sadis-format", s)
-}
-
-const (
-	_ SadisFormat = iota
-	SadisFormatAtt
-	SadisFormatDt
-	SadisFormatTt
-)
-
 type BBRCliOptions struct {
-	*BBSimYamlConfig
+	*GlobalConfig
 	BBSimIp      string
 	BBSimPort    string
 	BBSimApiPort string
 	LogFile      string
 }
 
-type BBSimYamlConfig struct {
+type GlobalConfig struct {
 	BBSim BBSimConfig
 	Olt   OltConfig
 	BBR   BBRConfig
@@ -120,32 +89,26 @@
 }
 
 type BBSimConfig struct {
-	DhcpRetry            bool          `yaml:"dhcp_retry"`
-	AuthRetry            bool          `yaml:"auth_retry"`
-	EnableIgmp           bool          `yaml:"enable_igmp"`
-	EnableDhcp           bool          `yaml:"enable_dhcp"`
-	EnableAuth           bool          `yaml:"enable_auth"`
-	LogLevel             string        `yaml:"log_level"`
-	LogCaller            bool          `yaml:"log_caller"`
-	Delay                int           `yaml:"delay"`
-	CpuProfile           *string       `yaml:"cpu_profile"`
-	CTagAllocation       TagAllocation `yaml:"c_tag_allocation"`
-	CTag                 int           `yaml:"c_tag"`
-	STagAllocation       TagAllocation `yaml:"s_tag_allocation"`
-	STag                 int           `yaml:"s_tag"`
-	OpenOltAddress       string        `yaml:"openolt_address"`
-	ApiAddress           string        `yaml:"api_address"`
-	RestApiAddress       string        `yaml:"rest_api_address"`
-	LegacyApiAddress     string        `yaml:"legacy_api_address"`
-	LegacyRestApiAddress string        `yaml:"legacy_rest_api_address"`
-	SadisRestAddress     string        `yaml:"sadis_rest_address"`
-	SadisServer          bool          `yaml:"sadis_server"`
-	SadisFormat          SadisFormat   `yaml:"sadis_format"`
-	KafkaAddress         string        `yaml:"kafka_address"`
-	Events               bool          `yaml:"enable_events"`
-	ControlledActivation string        `yaml:"controlled_activation"`
-	EnablePerf           bool          `yaml:"enable_perf"`
-	KafkaEventTopic      string        `yaml:"kafka_event_topic"`
+	ConfigFile           string
+	ServiceConfigFile    string
+	DhcpRetry            bool    `yaml:"dhcp_retry"`
+	AuthRetry            bool    `yaml:"auth_retry"`
+	LogLevel             string  `yaml:"log_level"`
+	LogCaller            bool    `yaml:"log_caller"`
+	Delay                int     `yaml:"delay"`
+	CpuProfile           *string `yaml:"cpu_profile"`
+	OpenOltAddress       string  `yaml:"openolt_address"`
+	ApiAddress           string  `yaml:"api_address"`
+	RestApiAddress       string  `yaml:"rest_api_address"`
+	LegacyApiAddress     string  `yaml:"legacy_api_address"`
+	LegacyRestApiAddress string  `yaml:"legacy_rest_api_address"`
+	SadisRestAddress     string  `yaml:"sadis_rest_address"`
+	SadisServer          bool    `yaml:"sadis_server"`
+	KafkaAddress         string  `yaml:"kafka_address"`
+	Events               bool    `yaml:"enable_events"`
+	ControlledActivation string  `yaml:"controlled_activation"`
+	EnablePerf           bool    `yaml:"enable_perf"`
+	KafkaEventTopic      string  `yaml:"kafka_event_topic"`
 }
 
 type BBRConfig struct {
@@ -154,24 +117,166 @@
 	LogCaller bool   `yaml:"log_caller"`
 }
 
-var Options *BBSimYamlConfig
-
-func init() {
-	// load settings from config file first
-	Options, _ = LoadBBSimConf("configs/bbsim.yaml")
+type ServiceYaml struct {
+	Name                string
+	CTag                int    `yaml:"c_tag"`
+	STag                int    `yaml:"s_tag"`
+	NeedsEapol          bool   `yaml:"needs_eapol"`
+	NeedsDchp           bool   `yaml:"needs_dhcp"`
+	NeedsIgmp           bool   `yaml:"needs_igmp"`
+	CTagAllocation      string `yaml:"c_tag_allocation"`
+	STagAllocation      string `yaml:"s_tag_allocation"`
+	TechnologyProfileID int    `yaml:"tp_id"`
+	UniTagMatch         int    `yaml:"uni_tag_match"`
+	ConfigureMacAddress bool   `yaml:"configure_mac_address"`
+	UsPonCTagPriority   int    `yaml:"us_pon_c_tag_priority"`
+	UsPonSTagPriority   int    `yaml:"us_pon_s_tag_priority"`
+	DsPonCTagPriority   int    `yaml:"ds_pon_c_tag_priority"`
+	DsPonSTagPriority   int    `yaml:"ds_pon_s_tag_priority"`
+}
+type YamlServiceConfig struct {
+	Workflow string
+	Services []ServiceYaml `yaml:"services,flow"`
 }
 
-func getDefaultOps() *BBSimYamlConfig {
+func (cfg *YamlServiceConfig) String() string {
+	str := fmt.Sprintf("[workflow: %s, Services: ", cfg.Workflow)
 
-	c := &BBSimYamlConfig{
+	for _, s := range cfg.Services {
+		str = fmt.Sprintf("%s[", str)
+		str = fmt.Sprintf("%sname=%s, c_tag=%d, s_tag=%d, ",
+			str, s.Name, s.CTag, s.STag)
+		str = fmt.Sprintf("%sc_tag_allocation=%s, s_tag_allocation=%s, ",
+			str, s.CTagAllocation, s.STagAllocation)
+		str = fmt.Sprintf("%sneeds_eapol=%t, needs_dhcp=%t, needs_igmp=%t",
+			str, s.NeedsEapol, s.NeedsDchp, s.NeedsIgmp)
+		str = fmt.Sprintf("%stp_id=%d, uni_tag_match=%d",
+			str, s.TechnologyProfileID, s.UniTagMatch)
+		str = fmt.Sprintf("%s]", str)
+	}
+	str = fmt.Sprintf("%s]", str)
+	return str
+}
+
+var (
+	Config   *GlobalConfig
+	Services []ServiceYaml
+)
+
+// Load the BBSim configuration. This is a combination of CLI parameters and YAML files
+// We proceed in this order:
+// - Read CLI parameters
+// - Using those we read the yaml files (config and services)
+// - we merge the configuration (CLI has priority over yaml files)
+func LoadConfig() {
+
+	Config = getDefaultOps()
+
+	cliConf := readCliParams()
+
+	yamlConf, err := loadBBSimConf(cliConf.BBSim.ConfigFile)
+
+	if err != nil {
+		log.WithFields(log.Fields{
+			"file": cliConf.BBSim.ConfigFile,
+			"err":  err,
+		}).Fatal("Can't read config file")
+	}
+
+	// merging Yaml and Default Values
+	if err := mergo.Merge(Config, yamlConf, mergo.WithOverride); err != nil {
+		log.WithFields(log.Fields{
+			"err": err,
+		}).Fatal("Can't merge YAML and Config")
+	}
+
+	// merging CLI values on top of the yaml ones
+	if err := mergo.Merge(Config, cliConf, mergo.WithOverride); err != nil {
+		log.WithFields(log.Fields{
+			"err": err,
+		}).Fatal("Can't merge CLI and Config")
+	}
+
+	services, err := loadBBSimServices(Config.BBSim.ServiceConfigFile)
+
+	if err != nil {
+		log.WithFields(log.Fields{
+			"file": Config.BBSim.ServiceConfigFile,
+			"err":  err,
+		}).Fatal("Can't read services file")
+	}
+
+	Services = services
+
+}
+
+func readCliParams() *GlobalConfig {
+
+	conf := GlobalConfig{}
+
+	configFile := flag.String("config", conf.BBSim.ConfigFile, "Configuration file path")
+	servicesFile := flag.String("services", conf.BBSim.ServiceConfigFile, "Service Configuration file path")
+
+	olt_id := flag.Int("olt_id", conf.Olt.ID, "OLT device ID")
+	nni := flag.Int("nni", int(conf.Olt.NniPorts), "Number of NNI ports per OLT device to be emulated")
+	pon := flag.Int("pon", int(conf.Olt.PonPorts), "Number of PON ports per OLT device to be emulated")
+	onu := flag.Int("onu", int(conf.Olt.OnusPonPort), "Number of ONU devices per PON port to be emulated")
+
+	openolt_address := flag.String("openolt_address", conf.BBSim.OpenOltAddress, "IP address:port")
+	api_address := flag.String("api_address", conf.BBSim.ApiAddress, "IP address:port")
+	rest_api_address := flag.String("rest_api_address", conf.BBSim.RestApiAddress, "IP address:port")
+
+	profileCpu := flag.String("cpuprofile", "", "write cpu profile to file")
+
+	logLevel := flag.String("logLevel", conf.BBSim.LogLevel, "Set the log level (trace, debug, info, warn, error)")
+	logCaller := flag.Bool("logCaller", conf.BBSim.LogCaller, "Whether to print the caller filename or not")
+
+	delay := flag.Int("delay", conf.BBSim.Delay, "The delay between ONU DISCOVERY batches in milliseconds (1 ONU per each PON PORT at a time")
+
+	controlledActivation := flag.String("ca", conf.BBSim.ControlledActivation, "Set the mode for controlled activation of PON ports and ONUs")
+	enablePerf := flag.Bool("enableperf", conf.BBSim.EnablePerf, "Setting this flag will cause BBSim to not store data like traffic schedulers, flows of ONUs etc..")
+	enableEvents := flag.Bool("enableEvents", conf.BBSim.Events, "Enable sending BBSim events on configured kafka server")
+	kafkaAddress := flag.String("kafkaAddress", conf.BBSim.KafkaAddress, "IP:Port for kafka")
+	kafkaEventTopic := flag.String("kafkaEventTopic", conf.BBSim.KafkaEventTopic, "Ability to configure the topic on which BBSim publishes events on Kafka")
+	dhcpRetry := flag.Bool("dhcpRetry", conf.BBSim.DhcpRetry, "Set this flag if BBSim should retry DHCP upon failure until success")
+	authRetry := flag.Bool("authRetry", conf.BBSim.AuthRetry, "Set this flag if BBSim should retry EAPOL (Authentication) upon failure until success")
+	flag.Parse()
+
+	conf.Olt.ID = int(*olt_id)
+	conf.Olt.NniPorts = uint32(*nni)
+	conf.Olt.PonPorts = uint32(*pon)
+	conf.Olt.OnusPonPort = uint32(*onu)
+	conf.BBSim.ConfigFile = *configFile
+	conf.BBSim.ServiceConfigFile = *servicesFile
+	conf.BBSim.CpuProfile = profileCpu
+	conf.BBSim.LogLevel = *logLevel
+	conf.BBSim.LogCaller = *logCaller
+	conf.BBSim.Delay = *delay
+	conf.BBSim.ControlledActivation = *controlledActivation
+	conf.BBSim.EnablePerf = *enablePerf
+	conf.BBSim.Events = *enableEvents
+	conf.BBSim.KafkaAddress = *kafkaAddress
+	conf.BBSim.OpenOltAddress = *openolt_address
+	conf.BBSim.ApiAddress = *api_address
+	conf.BBSim.RestApiAddress = *rest_api_address
+	conf.BBSim.KafkaEventTopic = *kafkaEventTopic
+	conf.BBSim.AuthRetry = *authRetry
+	conf.BBSim.DhcpRetry = *dhcpRetry
+
+	// update device id if not set
+	if conf.Olt.DeviceId == "" {
+		conf.Olt.DeviceId = net.HardwareAddr{0xA, 0xA, 0xA, 0xA, 0xA, byte(conf.Olt.ID)}.String()
+	}
+
+	return &conf
+}
+
+func getDefaultOps() *GlobalConfig {
+
+	c := &GlobalConfig{
 		BBSimConfig{
-			STagAllocation:       TagAllocationShared,
-			STag:                 900,
-			CTagAllocation:       TagAllocationUnique,
-			CTag:                 900,
-			EnableIgmp:           false,
-			EnableDhcp:           false,
-			EnableAuth:           false,
+			ConfigFile:           "configs/bbsim.yaml",
+			ServiceConfigFile:    "configs/att-services.yaml",
 			LogLevel:             "debug",
 			LogCaller:            false,
 			Delay:                200,
@@ -182,7 +287,6 @@
 			LegacyRestApiAddress: ":50073",
 			SadisRestAddress:     ":50074",
 			SadisServer:          true,
-			SadisFormat:          SadisFormatAtt,
 			KafkaAddress:         ":9092",
 			Events:               false,
 			ControlledActivation: "default",
@@ -214,131 +318,74 @@
 }
 
 // LoadBBSimConf loads the BBSim configuration from a YAML file
-func LoadBBSimConf(filename string) (*BBSimYamlConfig, error) {
+func loadBBSimConf(filename string) (*GlobalConfig, error) {
 	yamlConfig := getDefaultOps()
 
 	yamlFile, err := ioutil.ReadFile(filename)
 	if err != nil {
-		fmt.Printf("Cannot load BBSim configuration file: %s. Using defaults.\n", err)
+		log.WithFields(log.Fields{
+			"err":      err,
+			"filename": filename,
+		}).Error("Cannot load BBSim configuration file. Using defaults.")
 		return yamlConfig, nil
 	}
 
 	err = yaml.Unmarshal(yamlFile, yamlConfig)
 	if err != nil {
-		fmt.Printf("Error parsing YAML file: %s\n", err)
+		return nil, err
 	}
 
-	// TODO convert from string to TagAllocation
-
 	return yamlConfig, nil
 }
 
-// GetBBSimOpts loads the BBSim configuration file and over-rides options with corresponding CLI flags if set
-func GetBBSimOpts() *BBSimYamlConfig {
-	conf := Options
+// LoadBBSimServices parses a file describing the services that need to be created for each UNI
+func loadBBSimServices(filename string) ([]ServiceYaml, error) {
 
-	olt_id := flag.Int("olt_id", conf.Olt.ID, "OLT device ID")
-	nni := flag.Int("nni", int(conf.Olt.NniPorts), "Number of NNI ports per OLT device to be emulated")
-	pon := flag.Int("pon", int(conf.Olt.PonPorts), "Number of PON ports per OLT device to be emulated")
-	onu := flag.Int("onu", int(conf.Olt.OnusPonPort), "Number of ONU devices per PON port to be emulated")
+	yamlServiceCfg := YamlServiceConfig{}
 
-	openolt_address := flag.String("openolt_address", conf.BBSim.OpenOltAddress, "IP address:port")
-	api_address := flag.String("api_address", conf.BBSim.ApiAddress, "IP address:port")
-	rest_api_address := flag.String("rest_api_address", conf.BBSim.RestApiAddress, "IP address:port")
-
-	s_tag_allocation := flag.String("s_tag_allocation", conf.BBSim.STagAllocation.String(), "Use 'unique' for incremental values, 'shared' to use the same value in all the ONUs")
-	s_tag := flag.Int("s_tag", conf.BBSim.STag, "S-Tag initial value")
-
-	c_tag_allocation := flag.String("c_tag_allocation", conf.BBSim.CTagAllocation.String(), "Use 'unique' for incremental values, 'shared' to use the same value in all the ONUs")
-	c_tag := flag.Int("c_tag", conf.BBSim.CTag, "C-Tag starting value, each ONU will get a sequential one (targeting 1024 ONUs per BBSim instance the range is big enough)")
-
-	sadisFormat := flag.String("sadisFormat", conf.BBSim.SadisFormat.String(), fmt.Sprintf("Which format should sadis expose? [%s]", strings.Join(sadisFormatValues[1:], "|")))
-
-	auth := flag.Bool("auth", conf.BBSim.EnableAuth, "Set this flag if you want authentication to start automatically")
-	dhcp := flag.Bool("dhcp", conf.BBSim.EnableDhcp, "Set this flag if you want DHCP to start automatically")
-	igmp := flag.Bool("igmp", conf.BBSim.EnableIgmp, "Set this flag if you want IGMP to start automatically")
-	profileCpu := flag.String("cpuprofile", "", "write cpu profile to file")
-
-	logLevel := flag.String("logLevel", conf.BBSim.LogLevel, "Set the log level (trace, debug, info, warn, error)")
-	logCaller := flag.Bool("logCaller", conf.BBSim.LogCaller, "Whether to print the caller filename or not")
-
-	delay := flag.Int("delay", conf.BBSim.Delay, "The delay between ONU DISCOVERY batches in milliseconds (1 ONU per each PON PORT at a time")
-
-	controlledActivation := flag.String("ca", conf.BBSim.ControlledActivation, "Set the mode for controlled activation of PON ports and ONUs")
-	enablePerf := flag.Bool("enableperf", conf.BBSim.EnablePerf, "Setting this flag will cause BBSim to not store data like traffic schedulers, flows of ONUs etc..")
-	enableEvents := flag.Bool("enableEvents", conf.BBSim.Events, "Enable sending BBSim events on configured kafka server")
-	kafkaAddress := flag.String("kafkaAddress", conf.BBSim.KafkaAddress, "IP:Port for kafka")
-	kafkaEventTopic := flag.String("kafkaEventTopic", conf.BBSim.KafkaEventTopic, "Ability to configure the topic on which BBSim publishes events on Kafka")
-	dhcpRetry := flag.Bool("dhcpRetry", conf.BBSim.DhcpRetry, "Set this flag if BBSim should retry DHCP upon failure until success")
-	authRetry := flag.Bool("authRetry", conf.BBSim.AuthRetry, "Set this flag if BBSim should retry EAPOL (Authentication) upon failure until success")
-	flag.Parse()
-
-	sTagAlloc, err := tagAllocationFromString(*s_tag_allocation)
+	yamlFile, err := ioutil.ReadFile(filename)
 	if err != nil {
-		log.Fatal(err)
+		return nil, err
 	}
 
-	cTagAlloc, err := tagAllocationFromString(*c_tag_allocation)
+	err = yaml.Unmarshal([]byte(yamlFile), &yamlServiceCfg)
 	if err != nil {
-		log.Fatal(err)
+		return nil, err
 	}
 
-	sf, err := sadisFormatFromString(*sadisFormat)
-	if err != nil {
-		log.Fatal(err)
+	for _, service := range yamlServiceCfg.Services {
+
+		if service.CTagAllocation == "" || service.STagAllocation == "" {
+			log.Fatal("c_tag_allocation and s_tag_allocation are mandatory fields")
+		}
+
+		if _, err := tagAllocationFromString(string(service.CTagAllocation)); err != nil {
+			log.WithFields(log.Fields{
+				"err": err,
+			}).Fatal("c_tag_allocation is not valid")
+		}
 	}
 
-	if sf == SadisFormatTt {
-		log.Fatalf("Sadis format %s is not yet supported", sf.String())
-	}
-
-	conf.Olt.ID = int(*olt_id)
-	conf.Olt.NniPorts = uint32(*nni)
-	conf.Olt.PonPorts = uint32(*pon)
-	conf.Olt.OnusPonPort = uint32(*onu)
-	conf.BBSim.STagAllocation = sTagAlloc
-	conf.BBSim.STag = int(*s_tag)
-	conf.BBSim.CTagAllocation = cTagAlloc
-	conf.BBSim.CTag = int(*c_tag)
-	conf.BBSim.CpuProfile = profileCpu
-	conf.BBSim.LogLevel = *logLevel
-	conf.BBSim.LogCaller = *logCaller
-	conf.BBSim.EnableAuth = *auth
-	conf.BBSim.EnableDhcp = *dhcp
-	conf.BBSim.EnableIgmp = *igmp
-	conf.BBSim.Delay = *delay
-	conf.BBSim.ControlledActivation = *controlledActivation
-	conf.BBSim.EnablePerf = *enablePerf
-	conf.BBSim.Events = *enableEvents
-	conf.BBSim.KafkaAddress = *kafkaAddress
-	conf.BBSim.OpenOltAddress = *openolt_address
-	conf.BBSim.ApiAddress = *api_address
-	conf.BBSim.RestApiAddress = *rest_api_address
-	conf.BBSim.SadisFormat = sf
-	conf.BBSim.KafkaEventTopic = *kafkaEventTopic
-	conf.BBSim.AuthRetry = *authRetry
-	conf.BBSim.DhcpRetry = *dhcpRetry
-
-	// update device id if not set
-	if conf.Olt.DeviceId == "" {
-		conf.Olt.DeviceId = net.HardwareAddr{0xA, 0xA, 0xA, 0xA, 0xA, byte(conf.Olt.ID)}.String()
-	}
-
-	Options = conf
-	return conf
+	log.WithFields(log.Fields{
+		"services": yamlServiceCfg.String(),
+	}).Debug("BBSim services description correctly loaded")
+	return yamlServiceCfg.Services, nil
 }
 
+// This is only used by BBR
 func GetBBROpts() BBRCliOptions {
 
+	LoadConfig()
+
 	bbsimIp := flag.String("bbsimIp", "127.0.0.1", "BBSim IP")
 	bbsimPort := flag.String("bbsimPort", "50060", "BBSim Port")
 	bbsimApiPort := flag.String("bbsimApiPort", "50070", "BBSim API Port")
 	logFile := flag.String("logfile", "", "Log to a file")
 
-	options := GetBBSimOpts()
+	flag.Parse()
 
 	bbrOptions := BBRCliOptions{
-		options,
+		Config,
 		*bbsimIp,
 		*bbsimPort,
 		*bbsimApiPort,