From 3a48f4e32754d1d6874bc906a3b3a552af172a2b Mon Sep 17 00:00:00 2001
From: Andre Sterba <andre.sterba@stud.h-da.de>
Date: Wed, 24 Nov 2021 10:19:23 +0000
Subject: [PATCH] Use config package to handle all controller configurations

---
 .gitignore             |   3 ++
 api/initialise_test.go |   6 +--
 cmd/root.go            |  40 +++++++++++++--
 config.go              |  61 ----------------------
 config/config.go       | 114 +++++++++++++++++++++++++++++++++++++++++
 config/config_test.go  |  63 +++++++++++++++++++++++
 config_test.go         |  41 ---------------
 configs/.gitkeep       |   0
 controller.go          |  10 ++--
 initialise_test.go     |   6 +--
 nucleus/change_test.go |   5 +-
 11 files changed, 231 insertions(+), 118 deletions(-)
 delete mode 100644 config.go
 create mode 100644 config/config.go
 create mode 100644 config/config_test.go
 delete mode 100644 config_test.go
 create mode 100644 configs/.gitkeep

diff --git a/.gitignore b/.gitignore
index 2dcac5e10..b53fc7aa8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,6 @@ debug.test
 
 # Binary
 gosdn
+
+# Storage
+stores/
diff --git a/api/initialise_test.go b/api/initialise_test.go
index c6a00cc27..8da7ae364 100644
--- a/api/initialise_test.go
+++ b/api/initialise_test.go
@@ -5,11 +5,13 @@ import (
 	"net"
 	"os"
 	"testing"
+
 	"time"
 
 	cpb "code.fbi.h-da.de/danet/api/go/gosdn/core"
 	ppb "code.fbi.h-da.de/danet/api/go/gosdn/pnd"
 	tpb "code.fbi.h-da.de/danet/api/go/gosdn/transport"
+	"code.fbi.h-da.de/danet/gosdn/config"
 	"code.fbi.h-da.de/danet/gosdn/mocks"
 	nbi "code.fbi.h-da.de/danet/gosdn/northbound/server"
 	"code.fbi.h-da.de/danet/gosdn/nucleus"
@@ -128,9 +130,7 @@ func TestMain(m *testing.M) {
 }
 
 func bootstrapIntegrationTest() {
-	if os.Getenv("GOSDN_LOG") == "nolog" {
-		log.SetLevel(log.PanicLevel)
-	}
+	log.SetLevel(config.LogLevel)
 
 	addr := os.Getenv("GOSDN_TEST_ENDPOINT")
 	if addr != "" {
diff --git a/cmd/root.go b/cmd/root.go
index e7465b44f..7559bde87 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -34,6 +34,7 @@ package cmd
 import (
 	"context"
 	"os"
+	"path/filepath"
 
 	"code.fbi.h-da.de/danet/gosdn"
 
@@ -80,16 +81,22 @@ func init() {
 	rootCmd.Flags().StringVar(&csbiOrchestrstor, "csbi-orchestrator", "localhost:55056", "csbi orchestrator address")
 }
 
+const (
+	configHome string = "./configs"
+	configName string = "gosdn"
+	configType string = "toml"
+)
+
 // initConfig reads in config file and ENV variables if set.
 func initConfig() {
 	if cfgFile != "" {
 		// Use config file from the flag.
 		viper.SetConfigFile(cfgFile)
 	} else {
-		viper.AddConfigPath("./configs")
+		viper.AddConfigPath(configHome)
 		viper.AddConfigPath("/usr/local/etc/gosdn/")
-		viper.SetConfigType("toml")
-		viper.SetConfigName("gosdn")
+		viper.SetConfigType(configType)
+		viper.SetConfigName(configName)
 	}
 
 	viper.AutomaticEnv() // read in environment variables that match
@@ -97,6 +104,8 @@ func initConfig() {
 	// If a config file is found, read it in.
 	if err := viper.ReadInConfig(); err == nil {
 		log.Debug("Using config file:", viper.ConfigFileUsed())
+	} else {
+		ensureViperConfigFileExists()
 	}
 
 	viper.SetDefault("socket", ":55055")
@@ -118,3 +127,28 @@ func initConfig() {
 		log.SetReportCaller(false)
 	}
 }
+
+func ensureFileSystemStoreExists(pathToStore string) error {
+	emptyString := []byte("")
+	err := os.WriteFile(pathToStore, emptyString, 0600)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func ensureViperConfigFileExists() {
+	// Viper will crash if you call 'WriteConfig()' and the file does
+	// not exists yet.
+	// Therefore we handle this case here.
+	// Inspired by //https://github.com/spf13/viper/issues/430#issuecomment-661945101
+	configPath := filepath.Join(configHome, configName+"."+configType)
+
+	if _, err := os.Stat(configPath); os.IsNotExist(err) {
+		err := ensureFileSystemStoreExists(configPath)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
diff --git a/config.go b/config.go
deleted file mode 100644
index 9e09fcc91..000000000
--- a/config.go
+++ /dev/null
@@ -1,61 +0,0 @@
-package gosdn
-
-import (
-	"github.com/google/uuid"
-	"github.com/spf13/viper"
-)
-
-// Config represents the nucleus configuration
-type Config struct {
-	BasePndUUID        uuid.UUID
-	BaseSouthBoundType int32
-	BaseSouthBoundUUID uuid.UUID
-}
-
-func getUUIDFromViper(viperKey string) (uuid.UUID, error) {
-	UUIDAsString := viper.GetString(viperKey)
-	if UUIDAsString == "" {
-		newUUID := uuid.New()
-		viper.Set(viperKey, newUUID.String())
-		viper.WriteConfig()
-
-		return newUUID, nil
-	}
-
-	parsedUUID, err := uuid.Parse(UUIDAsString)
-	if err != nil {
-		return uuid.Nil, err
-	}
-
-	return parsedUUID, nil
-}
-
-// InitializeConfig loads the configuration
-func (c *Config) InitializeConfig() error {
-	var err error
-	basePNDUUIDKey := "basePNDUUID"
-	baseSouthBoundTypeKey := "baseSouthBoundType"
-	baseSouthBoundUUIDKey := "baseSouthBoundUUID"
-
-	basePNDUUID, err := getUUIDFromViper(basePNDUUIDKey)
-	if err != nil {
-		return err
-	}
-
-	c.BasePndUUID = basePNDUUID
-
-	baseSouthBoundUUID, err := getUUIDFromViper(baseSouthBoundUUIDKey)
-	if err != nil {
-		return err
-	}
-
-	c.BaseSouthBoundUUID = baseSouthBoundUUID
-
-	c.BaseSouthBoundType = viper.GetInt32("BaseSouthBoundType")
-	if c.BaseSouthBoundType != 0 {
-		viper.Set(baseSouthBoundTypeKey, 0)
-		viper.WriteConfig()
-	}
-
-	return nil
-}
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 000000000..86e39b687
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,114 @@
+package config
+
+import (
+	"os"
+	"time"
+
+	"github.com/google/uuid"
+	"github.com/sirupsen/logrus"
+	log "github.com/sirupsen/logrus"
+	"github.com/spf13/viper"
+)
+
+const (
+	defaultTimeOutDuration10minutes = time.Minute * 10
+	basePNDUUIDKey                  = "basePNDUUID"
+	baseSouthBoundTypeKey           = "baseSouthBoundType"
+	baseSouthBoundUUIDKey           = "baseSouthBoundUUID"
+	changeTimeoutKey                = "GOSDN_CHANGE_TIMEOUT"
+)
+
+// BasePndUUID is an uuid for the base PND
+var BasePndUUID uuid.UUID
+
+// BaseSouthBoundType is the type of the base SBI
+var BaseSouthBoundType int32
+
+// BaseSouthBoundUUID is an uuid for the base SBI
+var BaseSouthBoundUUID uuid.UUID
+
+// ChangeTimeout is the default timeout for a change
+var ChangeTimeout time.Duration
+
+// LogLevel ist the default log level
+var LogLevel logrus.Level
+
+// Init gets called on module import
+func Init() {
+	InitializeConfig()
+}
+
+// InitializeConfig loads the configuration
+func InitializeConfig() error {
+	var err error
+
+	basePNDUUIDFromViper, err := getUUIDFromViper(basePNDUUIDKey)
+	if err != nil {
+		return err
+	}
+
+	BasePndUUID = basePNDUUIDFromViper
+
+	baseSouthBoundUUIDFromViper, err := getUUIDFromViper(baseSouthBoundUUIDKey)
+	if err != nil {
+		return err
+	}
+
+	BaseSouthBoundUUID = baseSouthBoundUUIDFromViper
+
+	BaseSouthBoundType = viper.GetInt32(baseSouthBoundTypeKey)
+	if BaseSouthBoundType != 0 {
+		viper.Set(baseSouthBoundTypeKey, 0)
+		viper.WriteConfig()
+	}
+
+	err = setChangeTimeout()
+	if err != nil {
+		return err
+	}
+
+	setLogLevel()
+
+	return nil
+}
+
+func getUUIDFromViper(viperKey string) (uuid.UUID, error) {
+	UUIDAsString := viper.GetString(viperKey)
+	if UUIDAsString == "" {
+		newUUID := uuid.New()
+		viper.Set(viperKey, newUUID.String())
+		viper.WriteConfig()
+
+		return newUUID, nil
+	}
+
+	parsedUUID, err := uuid.Parse(UUIDAsString)
+	if err != nil {
+		return uuid.Nil, err
+	}
+
+	return parsedUUID, nil
+}
+
+func setChangeTimeout() error {
+	e := os.Getenv(changeTimeoutKey)
+	if e != "" {
+		changeTimeout, err := time.ParseDuration(e)
+		if err != nil {
+			log.Fatal(err)
+		}
+		ChangeTimeout = changeTimeout
+	} else {
+		ChangeTimeout = time.Minute * 10
+	}
+
+	return nil
+}
+
+func setLogLevel() {
+	if os.Getenv("GOSDN_LOG") == "nolog" {
+		LogLevel = logrus.PanicLevel
+	} else {
+		LogLevel = logrus.InfoLevel
+	}
+}
diff --git a/config/config_test.go b/config/config_test.go
new file mode 100644
index 000000000..6a1afccee
--- /dev/null
+++ b/config/config_test.go
@@ -0,0 +1,63 @@
+package config
+
+import (
+	"os"
+	"testing"
+	"time"
+
+	"github.com/sirupsen/logrus"
+	"github.com/spf13/viper"
+)
+
+func TestInit(t *testing.T) {
+	viper.SetConfigFile("./config_test.toml")
+	viper.Set("baseSouthBoundType", 0)
+	viper.Set("baseSouthBoundUUID", "bf8160d4-4659-4a1b-98fd-f409a04111eb")
+	viper.Set("basePNDUUID", "bf8160d4-4659-4a1b-98fd-f409a04111ec")
+	viper.Set("GOSDN_CHANGE_TIMEOUT", "10m")
+}
+
+func TestUseExistingConfig(t *testing.T) {
+	TestInit(t)
+
+	err := InitializeConfig()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	if BasePndUUID.String() != "bf8160d4-4659-4a1b-98fd-f409a04111ec" {
+		t.Fatalf("BasePndUUID.String() is not bf8160d4-4659-4a1b-98fd-f409a04111ec. got=%s",
+			BasePndUUID.String())
+	}
+
+	if BaseSouthBoundUUID.String() != "bf8160d4-4659-4a1b-98fd-f409a04111eb" {
+		t.Fatalf("BaseSouthBoundUUID.String() is not bf8160d4-4659-4a1b-98fd-f409a04111eb. got=%s",
+			BaseSouthBoundUUID.String())
+	}
+
+	if BaseSouthBoundType != 0 {
+		t.Fatalf("BaseSouthBoundType is not 0. got=%d",
+			BaseSouthBoundType)
+	}
+
+	testChangeTimeout, _ := time.ParseDuration("10m")
+	defaultChangeTimeout := defaultTimeOutDuration10minutes
+	if defaultChangeTimeout != testChangeTimeout {
+		t.Fatalf("ChangeTimeout is not 10ms. got=%v",
+			ChangeTimeout)
+	}
+
+	if os.Getenv("GOSDN_LOG") == "nolog" {
+		if LogLevel != logrus.PanicLevel {
+			t.Fatalf("LogLevel is not %v. got=%v",
+				logrus.PanicLevel, LogLevel)
+		}
+	} else {
+		if LogLevel != logrus.InfoLevel {
+			t.Fatalf("LogLevel is not %v. got=%v",
+				logrus.InfoLevel, LogLevel)
+		}
+	}
+
+}
diff --git a/config_test.go b/config_test.go
deleted file mode 100644
index 89bc5f276..000000000
--- a/config_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package gosdn
-
-import (
-	"testing"
-
-	"github.com/spf13/viper"
-)
-
-func Test_Init(t *testing.T) {
-	viper.SetConfigFile("./config_test.toml")
-	viper.Set("baseSouthBoundType", 0)
-	viper.Set("baseSouthBoundUUID", "bf8160d4-4659-4a1b-98fd-f409a04111eb")
-	viper.Set("basePNDUUID", "bf8160d4-4659-4a1b-98fd-f409a04111ec")
-}
-
-func Test_UseExistingConfig(t *testing.T) {
-	Test_Init(t)
-
-	testConfig := Config{}
-
-	err := testConfig.InitializeConfig()
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if testConfig.BasePndUUID.String() != "bf8160d4-4659-4a1b-98fd-f409a04111ec" {
-		t.Fatalf("testConfig.BasePndUUID.String() is not bf8160d4-4659-4a1b-98fd-f409a04111ec. got=%s",
-			testConfig.BasePndUUID.String())
-	}
-
-	if testConfig.BaseSouthBoundUUID.String() != "bf8160d4-4659-4a1b-98fd-f409a04111eb" {
-		t.Fatalf("testConfig.BaseSouthBoundUUID.String() is not bf8160d4-4659-4a1b-98fd-f409a04111eb. got=%s",
-			testConfig.BaseSouthBoundUUID.String())
-	}
-
-	if testConfig.BaseSouthBoundType != 0 {
-		t.Fatalf("testConfig.BaseSouthBoundType is not 0. got=%d",
-			testConfig.BaseSouthBoundType)
-	}
-}
diff --git a/configs/.gitkeep b/configs/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/controller.go b/controller.go
index d668c1753..748c7aa79 100644
--- a/controller.go
+++ b/controller.go
@@ -18,6 +18,7 @@ import (
 	cpb "code.fbi.h-da.de/danet/api/go/gosdn/csbi"
 	ppb "code.fbi.h-da.de/danet/api/go/gosdn/pnd"
 	spb "code.fbi.h-da.de/danet/api/go/gosdn/southbound"
+	"code.fbi.h-da.de/danet/gosdn/config"
 	"code.fbi.h-da.de/danet/gosdn/interfaces/southbound"
 	nbi "code.fbi.h-da.de/danet/gosdn/northbound/server"
 	"code.fbi.h-da.de/danet/gosdn/store"
@@ -61,13 +62,12 @@ func initialize() error {
 	startHttpServer()
 	coreLock.Unlock()
 
-	config := Config{}
 	err := config.InitializeConfig()
 	if err != nil {
 		return err
 	}
 
-	return createSouthboundInterfaces(config)
+	return createSouthboundInterfaces()
 }
 
 func startGrpc() error {
@@ -98,13 +98,13 @@ func startGrpc() error {
 }
 
 // createSouthboundInterfaces initializes the controller with its supported SBIs
-func createSouthboundInterfaces(config Config) error {
+func createSouthboundInterfaces() error {
 	sbi := nucleus.NewSBI(spb.Type(config.BaseSouthBoundType), config.BaseSouthBoundUUID)
-	return createPrincipalNetworkDomain(sbi, config)
+	return createPrincipalNetworkDomain(sbi)
 }
 
 // createPrincipalNetworkDomain initializes the controller with an initial PND
-func createPrincipalNetworkDomain(s southbound.SouthboundInterface, config Config) error {
+func createPrincipalNetworkDomain(s southbound.SouthboundInterface) error {
 	pnd, err := nucleus.NewPND("base", "gosdn base pnd", config.BasePndUUID, s, c.csbiClient, callback)
 	if err != nil {
 		return err
diff --git a/initialise_test.go b/initialise_test.go
index d47822bbd..bba39814b 100644
--- a/initialise_test.go
+++ b/initialise_test.go
@@ -4,6 +4,8 @@ import (
 	"os"
 	"testing"
 
+	"code.fbi.h-da.de/danet/gosdn/config"
+
 	"github.com/google/uuid"
 	log "github.com/sirupsen/logrus"
 )
@@ -17,10 +19,8 @@ var cuid uuid.UUID
 
 func TestMain(m *testing.M) {
 	log.SetReportCaller(true)
+	log.SetLevel(config.LogLevel)
 
-	if os.Getenv("GOSDN_LOG") == "nolog" {
-		log.SetLevel(log.PanicLevel)
-	}
 	readTestUUIDs()
 	os.Exit(m.Run())
 }
diff --git a/nucleus/change_test.go b/nucleus/change_test.go
index fa4cf5b9c..6fdff64fc 100644
--- a/nucleus/change_test.go
+++ b/nucleus/change_test.go
@@ -8,6 +8,7 @@ import (
 	"time"
 
 	ppb "code.fbi.h-da.de/danet/api/go/gosdn/pnd"
+	"code.fbi.h-da.de/danet/gosdn/config"
 	"github.com/google/uuid"
 	"github.com/openconfig/ygot/exampleoc"
 	"github.com/openconfig/ygot/ygot"
@@ -61,7 +62,7 @@ func TestChange_CommitRollback(t *testing.T) {
 		if err := c.Commit(); (err != nil) != wantErr {
 			t.Errorf("Commit() error = %v, wantErr %v", err, wantErr)
 		}
-		time.Sleep(time.Millisecond * 200)
+		time.Sleep(config.ChangeTimeout)
 	}()
 	got := <-callback
 	if !reflect.DeepEqual(got, want) {
@@ -103,7 +104,7 @@ func TestChange_CommitRollbackError(t *testing.T) {
 		if err := c.Commit(); (err != nil) != wantErr {
 			t.Errorf("Commit() error = %v, wantErr %v", err, wantErr)
 		}
-		time.Sleep(time.Millisecond * 200)
+		time.Sleep(config.ChangeTimeout)
 	}()
 	got := <-c.errChan
 	if !reflect.DeepEqual(got, want) {
-- 
GitLab