diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 30d61f93911db67cded1a818a6f3a4875e18c10f..df6d1fa57fa94a558e5f2694fb4644764cb0c914 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,35 +12,34 @@ before_script: code-quality-master: image: golangci/golangci-lint:latest-alpine stage: test - only: - - merge_requests - except: - variables: - - $CI_MERGE_REQUEST_TARGET_BRANCH_NAME != "master" + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH + - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' + when: manual script: - - git config --global url."https://$GO_MODULES_USER:$GO_MODULES_ACCESS_TOKEN@code.fbi.h-da.de".insteadOf "https://code.fbi.h-da.de" # writes golangci-lint output to gl-code-quality-report.json - - golangci-lint run --config .ci/.golangci-master.yml | tee gl-code-quality-report.json + - golangci-lint run --config .ci/.golangci-master.yml --out-format code-climate | tee gl-code-quality-report.json artifacts: reports: codequality: gl-code-quality-report.json + paths: + - gl-code-quality-report.json code-quality: image: golangci/golangci-lint:latest-alpine stage: test allow_failure: true - only: - - merge_requests - except: - variables: - - $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master" + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME != $CI_DEFAULT_BRANCH script: # writes golangci-lint output to gl-code-quality-report.json - - golangci-lint run --config .ci/.golangci.yml | tee gl-code-quality-report.json + - golangci-lint run --config .ci/.golangci.yml--out-format code-climate | tee gl-code-quality-report.json artifacts: reports: codequality: gl-code-quality-report.json + paths: + - gl-code-quality-report.json Documentation: before_script: @@ -50,9 +49,9 @@ Documentation: entrypoint: - '' stage: build - only: - changes: - - documentation/design/*.md + rules: + - changes: + - documentation/design/*.md script: - cd documentation/design - pandoc --filter pandoc-citeproc --bibliography=bibliography.bib --csl=acm-sig-proceedings.csl @@ -78,4 +77,3 @@ include: - template: Security/SAST.gitlab-ci.yml - template: Dependency-Scanning.gitlab-ci.yml - template: Security/License-Scanning.gitlab-ci.yml - diff --git a/clients.toml b/clients.toml new file mode 100644 index 0000000000000000000000000000000000000000..7a37fdcaac8aaa9d9563d2c591cf2a480943072b --- /dev/null +++ b/clients.toml @@ -0,0 +1,3 @@ +[[client]] +identifier = "ciena-mcp" +endpoint = "141.100.70.170:8080" \ No newline at end of file diff --git a/log/logger.go b/log/logger.go index 060ded548d286d1a863f6bd33d9d0b9edd809fb4..adcaa5dc590a7b46e9a4d0906afbd242e904a6ca 100644 --- a/log/logger.go +++ b/log/logger.go @@ -3,7 +3,12 @@ package log import ( "fmt" "io" + "log/syslog" "os" + "reflect" + "runtime" + "strconv" + "strings" "sync" "time" ) @@ -14,22 +19,47 @@ var once sync.Once // Logger is a wrapper for log.Logger and provides // methods to enable and disable logging. type Logger struct { - Out io.Writer - Loglevel Level - lock sync.Mutex + DefaultWriter io.Writer + LoglevelWriter map[Level]io.Writer + toSyslog map[Level]bool + Loglevel Level + lock sync.Mutex + builder strings.Builder +} + +func (l *Logger) buildMessage(level Level, syslog bool, args ...interface{}) { + if !syslog { + l.builder.WriteString(time.Now().Format(time.RFC3339)) + } + l.builder.WriteRune('\t') + l.builder.WriteString(prefix(level)) + l.builder.WriteRune('\t') + function, line := callers() + functionSplitted := strings.SplitAfter(function, "/") + function = functionSplitted[len(functionSplitted)-1] + l.builder.WriteString(function) + l.builder.WriteRune(':') + l.builder.WriteString(strconv.Itoa(line)) + l.builder.WriteRune('\t') + l.builder.WriteString(fmt.Sprint(args...)) + l.builder.WriteRune('\n') } func get() *Logger { once.Do(func() { logger = &Logger{ - Out: os.Stdout, - Loglevel: INFO, - lock: sync.Mutex{}, + DefaultWriter: os.Stderr, + LoglevelWriter: make(map[Level]io.Writer), + toSyslog: make(map[Level]bool), + Loglevel: INFO, + lock: sync.Mutex{}, } }) return logger } +//Loglevel sets the verbosity of the logger +//Defaults to INFO func Loglevel(level Level) { l := get() l.lock.Lock() @@ -37,48 +67,101 @@ func Loglevel(level Level) { l.Loglevel = level } +//Output defines the output of the logger +//Defaults to os.Stderr func Output(out io.Writer) { l := get() l.lock.Lock() defer l.lock.Unlock() - l.Out = out + l.DefaultWriter = out +} + +//LoglevelOutput defines a special output +//for a certain log level +func LoglevelOutput(level Level, out io.Writer) { + l := get() + l.lock.Lock() + defer l.lock.Unlock() + l.LoglevelWriter[level] = out + if reflect.TypeOf(out) == reflect.TypeOf(&syslog.Writer{}) { + l.toSyslog[level] = true + } } +//Debug passes the DEBUG flag and a +//message to the logger func Debug(args ...interface{}) { log(DEBUG, args...) } +//Debug passes the DEBUG flag and a +//message to the logger func Info(args ...interface{}) { log(INFO, args...) } +//Warn passes the WARNING flag and a +//message to the logger func Warn(args ...interface{}) { log(WARNING, args...) } +//Error passes the ERROR flag and a +//message to the logger func Error(args ...interface{}) { log(ERROR, args...) } +//Fatal passes the FATAL flag and a +//message to the logger and calls +//os.Exit(1) func Fatal(args ...interface{}) { log(FATAL, args...) + os.Exit(1) } +//Panic passes the PANIC flag and a +//message to the logger +//Also calls builtin.panic() func Panic(args ...interface{}) { log(PANIC, args...) + panic(args) } func log(level Level, args ...interface{}) { + defer func() { + if r := recover(); r != nil { + fmt.Println("Recovered in f", r) + } + }() l := get() l.lock.Lock() defer l.lock.Unlock() + defer l.builder.Reset() if level <= l.Loglevel { - msg := fmt.Sprint(args...) - logMessage := time.Now().Format(time.RFC3339) + "\t" + prefix(level) + "\t" + msg + "\n" - l.Out.Write([]byte(logMessage)) + l.buildMessage(level, l.toSyslog[level], args...) + msg := []byte(l.builder.String()) + writer, ok := l.LoglevelWriter[level] + var err error + if !ok { + _, err = l.DefaultWriter.Write(msg) + } else { + _, err = writer.Write(msg) + } + if err != nil { + panic(err) + } } } +func callers() (string, int) { + pc := make([]uintptr, 15) + n := runtime.Callers(5, pc) + frames := runtime.CallersFrames(pc[:n]) + frame, _ := frames.Next() + return frame.Function, frame.Line +} + func prefix(level Level) string { switch level { case PANIC: diff --git a/main.go b/main.go index 7527c7d3e71b2c3548031f90f24772174560a41c..800252d804efd745aac2b87bd45ee5ce3df6ec66 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "code.fbi.h-da.de/cocsn/gosdn/log" "code.fbi.h-da.de/cocsn/gosdn/nucleus" "flag" + "log/syslog" ) func main() { @@ -17,6 +18,16 @@ func main() { cliSocket := *cliListenAddr + ":" + *cliListenPort log.Loglevel(log.DEBUG) + syslogWriter, err := syslog.New(syslog.LOG_ALERT, "gosdn") + defer func() { + if err := syslogWriter.Close(); err != nil { + log.Fatal(err) + } + }() + if err != nil { + log.Fatal(err) + } + log.LoglevelOutput(log.INFO, syslogWriter) // Setup a channel to communicate if goSDN should shutdown. IsRunningChannel := make(chan bool) diff --git a/nucleus/controller.go b/nucleus/controller.go index 9cb0b781f6acb533db6c7d4a86e561f1e91869da..a0cdccbdf972faaa42b18d2cff72e23c6793c16a 100644 --- a/nucleus/controller.go +++ b/nucleus/controller.go @@ -18,6 +18,10 @@ type controllerConfig struct { ConfigPath string } +type clientConfigs struct { + Client []interfaces.ClientConfig `toml:"client"` +} + type Core struct { //Assert type with clients[key].(*MCPClient) clients map[string]interfaces.Client @@ -26,33 +30,22 @@ type Core struct { IsRunning chan bool } -func (c *Core) Init(socket, configfile string, IsRunningChannel chan bool) { - if configfile == "" { - configfile = "gosdn.toml" - } - _, err := os.Stat(configfile) - if err != nil { - log.Fatal("Config file is missing: ", configfile) - } - - c.config = controllerConfig{} - if _, err := toml.DecodeFile(configfile, &c.config); err != nil { +func (c *Core) Init(socket, configFileController, configFileClient string, IsRunningChannel chan bool) { + if err := c.readControllerConfig(configFileController); err != nil { log.Fatal(err) } + if socket != "localhost:55055" { c.config.CliSocket = socket } - if c.config.ConfigPath == "" { - c.config.ConfigPath = configfile - } c.AttachDatabase() c.IsRunning = IsRunningChannel - //TODO: Create client config/CLI adapter - c.clients["ciena-mcp"] = ciena.NewMCPClient("141.100.70.170:8080", "", "", &c.database) - + if err := c.readClientConfig(configFileClient); err != nil { + log.Fatal(err) + } } func (c *Core) AttachDatabase() { @@ -61,13 +54,8 @@ func (c *Core) AttachDatabase() { func (c *Core) Shutdown() { - stopIt := <- c.IsRunning - if !stopIt { - log.Debug("Shutdown() received action to shutdown") - }else { - log.Debug("Shutdown() received something else.") - } - + <- c.IsRunning + log.Info("Received shutdown signal. Shutting down") f, err := os.Create(c.config.ConfigPath) if err != nil { @@ -77,6 +65,42 @@ func (c *Core) Shutdown() { if err := enc.Encode(c.config); err != nil { log.Fatal(err) } - + log.Info("Shutdown complete") os.Exit(0) } + +func (c *Core)readControllerConfig(configFileController string) error { + if configFileController == "" { + configFileController = "gosdn.toml" + } + if _, err := os.Stat(configFileController); err != nil { + return err + } + + c.config = controllerConfig{} + if _, err := toml.DecodeFile(configFileController, &c.config); err != nil { + return err + } + + if c.config.ConfigPath == "" { + c.config.ConfigPath = configFileController + } + return nil +} + +func (c *Core)readClientConfig(configFileClient string) error { + if configFileClient == "" { + configFileClient = "clients.toml" + } + if _,err := os.Stat(configFileClient); err != nil { + return err + } + clients := clientConfigs{} + if _,err := toml.DecodeFile(configFileClient, &clients); err != nil { + return err + } + for _,client := range clients.Client { + c.clients[client.Identifier] = ciena.NewMCPClient(client.Endpoint, client.Username, client.Password, &c.database, &client) + } + return nil +} \ No newline at end of file diff --git a/nucleus/interfaces/client.go b/nucleus/interfaces/client.go index 1ada3063fe1ae8a8557d379b2d16bdc9f30efa81..9e541e687706e35d740cdc04ac1b3471a86b9be7 100644 --- a/nucleus/interfaces/client.go +++ b/nucleus/interfaces/client.go @@ -1,5 +1,5 @@ package interfaces type Client interface { - GetConfig() string + GetConfig() ClientConfig } diff --git a/nucleus/interfaces/clientConfig.go b/nucleus/interfaces/clientConfig.go new file mode 100644 index 0000000000000000000000000000000000000000..917537b7541ba7dedf5203228f756b4511297856 --- /dev/null +++ b/nucleus/interfaces/clientConfig.go @@ -0,0 +1,8 @@ +package interfaces + +type ClientConfig struct { + Identifier string `toml:"identifier"` + Endpoint string `toml:"endpoint"` + Username string `toml:"username"` + Password string `toml:"password"` +} diff --git a/nucleus/nucleus-core.go b/nucleus/nucleus-core.go index 20a504de5725d56ccc752bf5816c9c962bfed52d..1b63bcf3574ff23e730896b24977e7d082de0408 100644 --- a/nucleus/nucleus-core.go +++ b/nucleus/nucleus-core.go @@ -7,10 +7,7 @@ import ( "time" ) -/* - * This function is used to start the core of the controller and any auxiliary services. - */ - +//StartAndRun is used to start the core of the controller and any auxiliary services. func StartAndRun(socket, filename string, IsRunningChannel chan bool) { log.Info("This is the network superintendent...") log.Info("Starting my ducks") @@ -20,7 +17,7 @@ func StartAndRun(socket, filename string, IsRunningChannel chan bool) { clients: make(map[string]interfaces.Client), database: database.Database{}, } - core.Init(socket, filename, IsRunningChannel) + core.Init(socket, filename,"", IsRunningChannel) // Start the GRCP CLI go getCLIGoing(&core) go core.Shutdown() @@ -28,10 +25,8 @@ func StartAndRun(socket, filename string, IsRunningChannel chan bool) { log.Info("and ready for take off") //Just to produce some signs of vitality... - for (true) { + for { time.Sleep(10 * time.Second) log.Debug("Still alive...") } - - log.Info("Good bye....!") } diff --git a/restconf/client/ciena/client.go b/restconf/client/ciena/client.go index a384c683dd42af0550930f20f48e70e0b33105d3..d9af6d0247d776dc0d5a7f9771f2dc60fbb0dcc1 100644 --- a/restconf/client/ciena/client.go +++ b/restconf/client/ciena/client.go @@ -4,6 +4,7 @@ import ( "bytes" "code.fbi.h-da.de/cocsn/gosdn/database" "code.fbi.h-da.de/cocsn/gosdn/log" + "code.fbi.h-da.de/cocsn/gosdn/nucleus/interfaces" "code.fbi.h-da.de/cocsn/gosdn/restconf/util" apiclient "code.fbi.h-da.de/cocsn/swagger/apis/mcp/client" "crypto/tls" @@ -19,15 +20,15 @@ type MCPClient struct { client *apiclient.ServiceTopologyTAPI database *database.Database buffer *bytes.Buffer + config *interfaces.ClientConfig } -func (c MCPClient) GetConfig() string { - //TODO: Fill with life - return "..." +func (c MCPClient) GetConfig() interfaces.ClientConfig { + return *c.config } //NewMCPClient creates a client -func NewMCPClient(endpoint, username, password string, database *database.Database) *MCPClient { +func NewMCPClient(endpoint, username, password string, database *database.Database, config *interfaces.ClientConfig) *MCPClient { // create the transport transport := httptransport.New(endpoint, "/", nil) transport.Transport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} @@ -45,6 +46,7 @@ func NewMCPClient(endpoint, username, password string, database *database.Databa client: client, database: database, buffer: buffer, + config: config, } }