Refactor invoker management
Issue-ID: NONRTRIC-814
Signed-off-by: elinuxhenrik <henrik.b.andersson@est.tech>
Change-Id: I645cce6b25b0455d23d12e9b2e00234b20ef6bf9
diff --git a/capifcore/internal/invokermanagement/invokermanagement.go b/capifcore/internal/invokermanagement/invokermanagement.go
index 9025f83..5fa5ce6 100644
--- a/capifcore/internal/invokermanagement/invokermanagement.go
+++ b/capifcore/internal/invokermanagement/invokermanagement.go
@@ -21,10 +21,10 @@
package invokermanagement
import (
+ "errors"
+ "fmt"
"net/http"
"path"
- "strconv"
- "strings"
"sync"
"oransc.org/nonrtric/capifcore/internal/eventsapi"
@@ -103,14 +103,13 @@
// Creates a new individual API Invoker profile.
func (im *InvokerManager) PostOnboardedInvokers(ctx echo.Context) error {
var newInvoker invokerapi.APIInvokerEnrolmentDetails
- err := ctx.Bind(&newInvoker)
- if err != nil {
- return sendCoreError(ctx, http.StatusBadRequest, "Invalid format for invoker")
+ errMsg := "Unable to onboard invoker due to %s"
+ if err := ctx.Bind(&newInvoker); err != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, "invalid format for invoker"))
}
- shouldReturn, coreError := im.validateInvoker(newInvoker, ctx)
- if shouldReturn {
- return coreError
+ if err := im.validateInvoker(newInvoker, ctx); err != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
}
im.prepareNewInvoker(&newInvoker)
@@ -119,7 +118,7 @@
uri := ctx.Request().Host + ctx.Request().URL.String()
ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, *newInvoker.ApiInvokerId))
- err = ctx.JSON(http.StatusCreated, newInvoker)
+ err := ctx.JSON(http.StatusCreated, newInvoker)
if err != nil {
// Something really bad happened, tell Echo that our handler failed
return err
@@ -129,26 +128,15 @@
}
func (im *InvokerManager) prepareNewInvoker(newInvoker *invokerapi.APIInvokerEnrolmentDetails) {
- im.lock.Lock()
- defer im.lock.Unlock()
-
- newInvoker.ApiInvokerId = im.getId(newInvoker.ApiInvokerInformation)
- newInvoker.OnboardingInformation.OnboardingSecret = getOnboardingSecret(*newInvoker)
-
var apiList invokerapi.APIList = im.publishRegister.GetAllPublishedServices()
newInvoker.ApiList = &apiList
- im.onboardedInvokers[*newInvoker.ApiInvokerId] = *newInvoker
-}
+ im.lock.Lock()
+ defer im.lock.Unlock()
-func getOnboardingSecret(newInvoker invokerapi.APIInvokerEnrolmentDetails) *string {
- onboardingSecret := "onboarding_secret_"
- if newInvoker.ApiInvokerInformation != nil {
- onboardingSecret = onboardingSecret + strings.ReplaceAll(*newInvoker.ApiInvokerInformation, " ", "_")
- } else {
- onboardingSecret = onboardingSecret + *newInvoker.ApiInvokerId
- }
- return &onboardingSecret
+ newInvoker.PrepareNewInvoker()
+
+ im.onboardedInvokers[*newInvoker.ApiInvokerId] = *newInvoker
}
// Deletes an individual API Invoker.
@@ -171,18 +159,17 @@
// Updates an individual API invoker details.
func (im *InvokerManager) PutOnboardedInvokersOnboardingId(ctx echo.Context, onboardingId string) error {
var invoker invokerapi.APIInvokerEnrolmentDetails
- err := ctx.Bind(&invoker)
- if err != nil {
- return sendCoreError(ctx, http.StatusBadRequest, "Invalid format for invoker")
+ errMsg := "Unable to update invoker due to %s"
+ if err := ctx.Bind(&invoker); err != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, "invalid format for invoker"))
}
if onboardingId != *invoker.ApiInvokerId {
- return sendCoreError(ctx, http.StatusBadRequest, "Invoker ApiInvokerId not matching")
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, "ApiInvokerId not matching"))
}
- shouldReturn, coreError := im.validateInvoker(invoker, ctx)
- if shouldReturn {
- return coreError
+ if err := im.validateInvoker(invoker, ctx); err != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
}
if _, ok := im.onboardedInvokers[onboardingId]; ok {
@@ -191,7 +178,7 @@
return sendCoreError(ctx, http.StatusNotFound, "The invoker to update has not been onboarded")
}
- err = ctx.JSON(http.StatusOK, invoker)
+ err := ctx.JSON(http.StatusOK, invoker)
if err != nil {
// Something really bad happened, tell Echo that our handler failed
return err
@@ -210,20 +197,15 @@
return ctx.NoContent(http.StatusNotImplemented)
}
-func (im *InvokerManager) validateInvoker(invoker invokerapi.APIInvokerEnrolmentDetails, ctx echo.Context) (bool, error) {
- if invoker.NotificationDestination == "" {
- return true, sendCoreError(ctx, http.StatusBadRequest, "Invoker missing required NotificationDestination")
+func (im *InvokerManager) validateInvoker(invoker invokerapi.APIInvokerEnrolmentDetails, ctx echo.Context) error {
+ if err := invoker.Validate(); err != nil {
+ return err
}
-
- if invoker.OnboardingInformation.ApiInvokerPublicKey == "" {
- return true, sendCoreError(ctx, http.StatusBadRequest, "Invoker missing required OnboardingInformation.ApiInvokerPublicKey")
- }
-
if !im.areAPIsPublished(invoker.ApiList) {
- return true, sendCoreError(ctx, http.StatusBadRequest, "Some APIs needed by invoker are not registered")
+ return errors.New("some APIs needed by invoker are not registered")
}
- return false, nil
+ return nil
}
func (im *InvokerManager) areAPIsPublished(apis *invokerapi.APIList) bool {
@@ -233,17 +215,6 @@
return im.publishRegister.AreAPIsPublished((*[]publishapi.ServiceAPIDescription)(apis))
}
-func (im *InvokerManager) getId(invokerInfo *string) *string {
- idAsString := "api_invoker_id_"
- if invokerInfo != nil {
- idAsString = idAsString + strings.ReplaceAll(*invokerInfo, " ", "_")
- } else {
- idAsString = idAsString + strconv.FormatInt(im.nextId, 10)
- im.nextId = im.nextId + 1
- }
- return &idAsString
-}
-
func (im *InvokerManager) sendEvent(invokerId string, eventType eventsapi.CAPIFEvent) {
invokerIds := []string{invokerId}
event := eventsapi.EventNotification{
diff --git a/capifcore/internal/invokermanagement/invokermanagement_test.go b/capifcore/internal/invokermanagement/invokermanagement_test.go
index 4613cc4..d3c386c 100644
--- a/capifcore/internal/invokermanagement/invokermanagement_test.go
+++ b/capifcore/internal/invokermanagement/invokermanagement_test.go
@@ -80,7 +80,7 @@
assert.True(t, invokerUnderTest.VerifyInvokerSecret(wantedInvokerId, wantedInvokerSecret))
publishRegisterMock.AssertCalled(t, "GetAllPublishedServices")
assert.Equal(t, invokermanagementapi.APIList(publishedServices), *resultInvoker.ApiList)
- if invokerEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
+ if invokerEvent, timeout := waitForEvent(eventChannel, 1*time.Second); timeout {
assert.Fail(t, "No event sent")
} else {
assert.Equal(t, *resultInvoker.ApiInvokerId, (*invokerEvent.EventDetail.ApiInvokerIds)[0])
@@ -138,7 +138,7 @@
assert.Equal(t, http.StatusNoContent, result.Code())
assert.False(t, invokerUnderTest.IsInvokerRegistered(invokerId))
- if invokerEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
+ if invokerEvent, timeout := waitForEvent(eventChannel, 1*time.Second); timeout {
assert.Fail(t, "No event sent")
} else {
assert.Equal(t, invokerId, (*invokerEvent.EventDetail.ApiInvokerIds)[0])
@@ -220,7 +220,7 @@
assert.Contains(t, *problemDetails.Cause, "not matching")
assert.Contains(t, *problemDetails.Cause, "ApiInvokerId")
- // Update an invoker that has not been onboarded, shold get 404 with problem details
+ // Update an invoker that has not been onboarded, should get 404 with problem details
missingId := "1"
invoker.ApiInvokerId = &missingId
result = testutil.NewRequest().Put("/onboardedInvokers/"+missingId).WithJsonBody(invoker).Go(t, requestHandler)
diff --git a/capifcore/internal/invokermanagementapi/typeupdate.go b/capifcore/internal/invokermanagementapi/typeupdate.go
new file mode 100644
index 0000000..1de6280
--- /dev/null
+++ b/capifcore/internal/invokermanagementapi/typeupdate.go
@@ -0,0 +1,59 @@
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2023: Nordix Foundation
+// %%
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================LICENSE_END===================================
+//
+
+package invokermanagementapi
+
+import (
+ "strings"
+
+ "github.com/google/uuid"
+)
+
+var uuidFunc = getUUID
+
+func (ied *APIInvokerEnrolmentDetails) PrepareNewInvoker() {
+ ied.createId()
+ ied.getOnboardingSecret()
+
+}
+
+func (ied *APIInvokerEnrolmentDetails) createId() {
+ idAsString := "api_invoker_id_"
+ if ied.ApiInvokerInformation != nil {
+ idAsString = idAsString + strings.ReplaceAll(*ied.ApiInvokerInformation, " ", "_")
+ } else {
+ idAsString = idAsString + uuidFunc()
+ }
+ ied.ApiInvokerId = &idAsString
+}
+
+func getUUID() string {
+ return uuid.NewString()
+}
+
+func (ied *APIInvokerEnrolmentDetails) getOnboardingSecret() {
+ onboardingSecret := "onboarding_secret_"
+ if ied.ApiInvokerInformation != nil {
+ onboardingSecret = onboardingSecret + strings.ReplaceAll(*ied.ApiInvokerInformation, " ", "_")
+ } else {
+ onboardingSecret = onboardingSecret + *ied.ApiInvokerId
+ }
+ ied.OnboardingInformation.OnboardingSecret = &onboardingSecret
+}
diff --git a/capifcore/internal/invokermanagementapi/typeupdate_test.go b/capifcore/internal/invokermanagementapi/typeupdate_test.go
new file mode 100644
index 0000000..5128d5f
--- /dev/null
+++ b/capifcore/internal/invokermanagementapi/typeupdate_test.go
@@ -0,0 +1,44 @@
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2023: Nordix Foundation
+// %%
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================LICENSE_END===================================
+//
+
+package invokermanagementapi
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPrepareNewInvoker(t *testing.T) {
+ invokerUnderTest := APIInvokerEnrolmentDetails{}
+ uuidFunc = func() string {
+ return "1"
+ }
+
+ invokerUnderTest.PrepareNewInvoker()
+ assert.Equal(t, "api_invoker_id_1", *invokerUnderTest.ApiInvokerId)
+ assert.Equal(t, "onboarding_secret_api_invoker_id_1", *invokerUnderTest.OnboardingInformation.OnboardingSecret)
+
+ invokerInfo := "invoker info"
+ invokerUnderTest.ApiInvokerInformation = &invokerInfo
+ invokerUnderTest.PrepareNewInvoker()
+ assert.Equal(t, "api_invoker_id_invoker_info", *invokerUnderTest.ApiInvokerId)
+ assert.Equal(t, "onboarding_secret_invoker_info", *invokerUnderTest.OnboardingInformation.OnboardingSecret)
+}
diff --git a/capifcore/internal/invokermanagementapi/typevalidation.go b/capifcore/internal/invokermanagementapi/typevalidation.go
new file mode 100644
index 0000000..b12ddfb
--- /dev/null
+++ b/capifcore/internal/invokermanagementapi/typevalidation.go
@@ -0,0 +1,37 @@
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2023: Nordix Foundation
+// %%
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================LICENSE_END===================================
+//
+
+package invokermanagementapi
+
+import (
+ "errors"
+)
+
+func (ied *APIInvokerEnrolmentDetails) Validate() error {
+ if ied.NotificationDestination == "" {
+ return errors.New("APIInvokerEnrolmentDetails missing required NotificationDestination")
+ }
+
+ if ied.OnboardingInformation.ApiInvokerPublicKey == "" {
+ return errors.New("APIInvokerEnrolmentDetails missing required OnboardingInformation.ApiInvokerPublicKey")
+ }
+
+ return nil
+}
diff --git a/capifcore/internal/invokermanagementapi/typevalidation_test.go b/capifcore/internal/invokermanagementapi/typevalidation_test.go
new file mode 100644
index 0000000..60e4ee5
--- /dev/null
+++ b/capifcore/internal/invokermanagementapi/typevalidation_test.go
@@ -0,0 +1,48 @@
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2023: Nordix Foundation
+// %%
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================LICENSE_END===================================
+//
+
+package invokermanagementapi
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestValidateInvoker(t *testing.T) {
+ invokerUnderTest := APIInvokerEnrolmentDetails{}
+
+ err := invokerUnderTest.Validate()
+ if assert.Error(t, err) {
+ assert.Contains(t, err.Error(), "missing")
+ assert.Contains(t, err.Error(), "NotificationDestination")
+ }
+
+ invokerUnderTest.NotificationDestination = "destination"
+ err = invokerUnderTest.Validate()
+ if assert.Error(t, err) {
+ assert.Contains(t, err.Error(), "missing")
+ assert.Contains(t, err.Error(), "OnboardingInformation.ApiInvokerPublicKey")
+ }
+
+ invokerUnderTest.OnboardingInformation.ApiInvokerPublicKey = "key"
+ err = invokerUnderTest.Validate()
+ assert.Nil(t, err)
+}