From 290ccc06c17c9ff559d9c5e4b0f1ec6a307fdba6 Mon Sep 17 00:00:00 2001
From: Manuel Kieweg <manuel.kieweg@h-da.de>
Date: Mon, 29 Mar 2021 10:02:36 +0100
Subject: [PATCH] fix race conditions

---
 nucleus/controller.go      |  6 ++--
 nucleus/controller_test.go | 56 +++++++++++++++++++++++---------------
 nucleus/http.go            |  7 ++++-
 nucleus/store.go           | 14 +++++++++-
 4 files changed, 57 insertions(+), 26 deletions(-)

diff --git a/nucleus/controller.go b/nucleus/controller.go
index fff5b4cc8..bb928f4ae 100644
--- a/nucleus/controller.go
+++ b/nucleus/controller.go
@@ -12,6 +12,9 @@ import (
 	"time"
 )
 
+var coreLock sync.RWMutex
+var coreOnce sync.Once
+
 // Core is the representation of the controllers core
 type Core struct {
 	// deprecated
@@ -84,8 +87,7 @@ func createPrincipalNetworkDomain(sbi SouthboundInterface) error {
 // Run calls initialize to start the controller
 func Run(ctx context.Context) error {
 	var initError error
-	var once sync.Once
-	once.Do(func() {
+	coreOnce.Do(func() {
 		initError = initialize()
 	})
 	if initError != nil {
diff --git a/nucleus/controller_test.go b/nucleus/controller_test.go
index 4608dc568..c414d6c61 100644
--- a/nucleus/controller_test.go
+++ b/nucleus/controller_test.go
@@ -15,43 +15,55 @@ func TestRun(t *testing.T) {
 		name    string
 		args    args
 		want    interface{}
-		wantErr bool
 	}{
 		{
 			name:    "liveliness indicator",
 			args:    args{request: apiEndpoint + "/livez"},
 			want:    http.StatusOK,
-			wantErr: false,
 		},
 		{
 			name:    "readyness indicator",
 			args:    args{request: apiEndpoint + "/readyz"},
 			want:    http.StatusOK,
-			wantErr: false,
 		},
 		{
 			name:    "init",
 			args:    args{request: apiEndpoint + "/api?q=init"},
 			want:    http.StatusOK,
-			wantErr: false,
 		},
 	}
-	for _, tt := range tests {
-		ctx, cancel := context.WithCancel(context.Background())
-		go func() {
-			if err := Run(ctx); (err != nil) != tt.wantErr {
-				t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr)
-			}
-		}()
-		t.Run(tt.name, func(t *testing.T) {
-			got, err := http.Get(tt.args.request)
-			if err != nil {
-				t.Error(err)
-			}
-			if !reflect.DeepEqual(got.StatusCode, tt.want) {
-				t.Errorf("Run() got: %v, want %v", got.StatusCode, tt.want)
-			}
-		})
-		cancel()
-	}
+	ctx, cancel := context.WithCancel(context.Background())
+	go func() {
+		if err := Run(ctx); err != nil{
+			t.Errorf("Run() error = %v", err)
+		}
+	}()
+	t.Run("Controller Start HTTP API", func(t *testing.T) {
+		got, err := http.Get(tests[0].args.request)
+		if err != nil {
+			t.Error(err)
+			return
+		}
+		if !reflect.DeepEqual(got.StatusCode, tests[0].want) {
+			t.Errorf("Run() got: %v, want %v", got.StatusCode, tests[0].want)
+		}
+		got, err = http.Get(tests[0].args.request)
+		if err != nil {
+			t.Error(err)
+			return
+		}
+		if !reflect.DeepEqual(got.StatusCode, tests[1].want) {
+			t.Errorf("Run() got: %v, want %v", got.StatusCode, tests[1].want)
+		}
+		got, err = http.Get(tests[0].args.request)
+		if err != nil {
+			t.Error(err)
+			return
+		}
+		if !reflect.DeepEqual(got.StatusCode, tests[2].want) {
+			t.Errorf("Run() got: %v, want %v", got.StatusCode, tests[2].want)
+		}
+	})
+
+	cancel()
 }
diff --git a/nucleus/http.go b/nucleus/http.go
index dc2df371c..41fea9aaa 100644
--- a/nucleus/http.go
+++ b/nucleus/http.go
@@ -9,9 +9,12 @@ import (
 	log "github.com/sirupsen/logrus"
 	"net/http"
 	"net/url"
+	"sync"
 	"time"
 )
 
+var httpOnce sync.Once
+
 func stopHttpServer() error {
 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 	defer cancel()
@@ -32,7 +35,9 @@ func registerHttpHandler() {
 
 // deprecated
 func httpAPI() (err error) {
-	registerHttpHandler()
+	coreLock.Lock()
+	defer coreLock.Unlock()
+	httpOnce.Do(registerHttpHandler)
 	c.httpServer = &http.Server{Addr: ":8080"}
 	go func() {
 		err = c.httpServer.ListenAndServe()
diff --git a/nucleus/store.go b/nucleus/store.go
index 14d09d335..a1fbdab76 100644
--- a/nucleus/store.go
+++ b/nucleus/store.go
@@ -4,8 +4,11 @@ import (
 	"github.com/google/uuid"
 	log "github.com/sirupsen/logrus"
 	"reflect"
+	"sync"
 )
 
+var storeLock sync.RWMutex
+
 // Storable provides an interface for the controller's storage architecture.
 type Storable interface {
 	ID() uuid.UUID
@@ -14,6 +17,8 @@ type Storable interface {
 type store map[uuid.UUID]Storable
 
 func (s store) exists(id uuid.UUID) bool {
+	storeLock.RLock()
+	defer storeLock.RUnlock()
 	_, ok := s[id]
 	return ok
 }
@@ -22,7 +27,9 @@ func (s store) add(item Storable) error {
 	if s.exists(item.ID()) {
 		return &ErrAlreadyExists{item: item}
 	}
+	storeLock.Lock()
 	s[item.ID()] = item
+	storeLock.Unlock()
 	log.WithFields(log.Fields{
 		"type": reflect.TypeOf(item),
 		"uuid": item.ID(),
@@ -37,6 +44,8 @@ func (s store) get(id uuid.UUID) (Storable, error) {
 	log.WithFields(log.Fields{
 		"uuid": id,
 	}).Info("storable was accessed")
+	storeLock.RLock()
+	defer storeLock.RUnlock()
 	return s[id], nil
 }
 
@@ -44,7 +53,9 @@ func (s store) delete(id uuid.UUID) error {
 	if !s.exists(id) {
 		return &ErrNotFound{id: id}
 	}
+	storeLock.Lock()
 	delete(s, id)
+	storeLock.Unlock()
 	log.WithFields(log.Fields{
 		"uuid": id,
 	}).Info("storable has been deleted")
@@ -52,8 +63,9 @@ func (s store) delete(id uuid.UUID) error {
 }
 
 func (s store) UUIDs() []uuid.UUID {
+	storeLock.RLock()
+	defer storeLock.RUnlock()
 	keys := make([]uuid.UUID, len(s))
-
 	i := 0
 	for k := range s {
 		keys[i] = k
-- 
GitLab