To run the Core Function from the command line, run the following commands from this folder. For the parameter `chartMuseumUrl`, if it is not provided CAPIF Core will not do any Helm integration, i.e. try to start any Halm chart when publishing a service.
- ./capifcore [-port <port (default 8090)>] [-chartMuseumUrl <URL to ChartMuseum>] [-repoName <Helm repo name (default capifcore)>] [-loglevel <log level (default Info)>]
+ ./capifcore [-port <port (default 8090)>] [-secPort <Secure port (default 4433)>] [-chartMuseumUrl <URL to ChartMuseum>] [-repoName <Helm repo name (default capifcore)>] [-loglevel <log level (default Info)>] [-certPath <Path to certificate>] [-keyPath <Path to private key>]
To run CAPIF Core as a K8s pod together with ChartMuseum, start and stop scripts are provided. The pod configurations are provided in the `configs` folder. CAPIF Core is then available on port `31570`.
--- /dev/null
+-----BEGIN CERTIFICATE-----
+MIICMzCCAZygAwIBAgIRAI4ZifW8kkZA8erUGGlExBIwDQYJKoZIhvcNAQELBQAw
+EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
+MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEAvjU9/c+2dpoRRMnJPMh9eGAWA0cfg2h+AVotSVCqgJ+Hlr91BWCE0dDV
+nIlNGVXZSkxI4rRTI3DZi8wdEWNeiPBIQDbUpNDofCZ/AeAMfzhMb3cyMMZcZMG6
+Zx0aXvEdZhAmJjBUAT1+XrIAegLQvhN2g9awcaWVkuxoawG2HF0CAwEAAaOBhjCB
+gzAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQUuhJx8jwPJYB3Zg2Aaa4ZsVZ78v8wLAYDVR0RBCUw
+I4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEB
+CwUAA4GBAKHALs8ifFaoVQw0GaCmIQt/yS9eWcssGGJHmAMyXTn78wsjnTdySDjv
+ZG7naV3uFs1ffA8eci5p1Hjzt8JFFGfLgHaoqnZW84+giwGI0RJKLz1dwnSsoHBz
+VKxIPMRm2xkQTiOMWX5YlbhiQf5rbx2OEaOqscM2H1DEwXSXFtjd
+-----END CERTIFICATE-----
--- /dev/null
+-----BEGIN PRIVATE KEY-----
+MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL41Pf3PtnaaEUTJ
+yTzIfXhgFgNHH4NofgFaLUlQqoCfh5a/dQVghNHQ1ZyJTRlV2UpMSOK0UyNw2YvM
+HRFjXojwSEA21KTQ6HwmfwHgDH84TG93MjDGXGTBumcdGl7xHWYQJiYwVAE9fl6y
+AHoC0L4TdoPWsHGllZLsaGsBthxdAgMBAAECgYEAklky5mwAT0cBzHSZ4qu8Znc/
+2KvLoncupGm2+HcZiTe1wpZzOnzmFO3ivbui18CHHLSPS+dFJLq6l+an4u4bGFu7
+HrbZPwPqrlPHt/sSrPlhk7J/bLqwdhIGgHZje5XYZUqobhRdRR505Lqz20eEZeax
+zzgIa2v7uiYmh/COQsECQQDr92Refg3w32QEvtmnumcq7Q3Rdy/8ol8kqP/LeaV6
+rYHV23Dj5qV3HXJpL7DOKNOkzYnfZ7tN7Ur0p5xANVwRAkEAzltQl6eCiVWtYO6G
+cTf5LLBYuhu7KJD2LCFwWeNdAF652yczunIc8K8UgVkuPPVMzP/fRxvv7+vfo6Lx
+9p73jQJALRBeFr20I+BF1bItFx8+PLBxByPgAjtwOCweTdm5hKhGN3VlJeESkKEL
+DJOTDIw3fy3RutywpL1Ap2CrMof+QQJAcjqOFET/t3Ib9YpUFZw8bIZ5txvesIf+
+HVOtU7TOKIRHMY8zzUOZzYm9OhTZyZioGNqTCFPor9DMDVMHydMZiQJBAN0Cd3SO
+RyyC7drmNy7oCdZe+WSKUHvgoE/J8y91AK7FREiPMgEEQSeOe7wPVzHuzDfWGRSu
+0CYjIAUsES7Oizc=
+-----END PRIVATE KEY-----
--- /dev/null
+// -
+// ========================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 eventsapi
+
+import (
+ "errors"
+ "fmt"
+ "net/url"
+ "strings"
+)
+
+func (es EventSubscription) Validate() error {
+ if len(es.Events) == 0 {
+ return errors.New("required attribute EventSubscription:events must contain at least one element")
+ }
+
+ for _, event := range es.Events {
+ if err := validateEvent(event); err != nil {
+ return errors.New("EventSubscription events contains invalid event")
+ }
+ }
+
+ if len(strings.TrimSpace(string(es.NotificationDestination))) == 0 {
+ return errors.New("EventSubscription missing required notificationDestination")
+ }
+ if _, err := url.ParseRequestURI(string(es.NotificationDestination)); err != nil {
+ return fmt.Errorf("APIInvokerEnrolmentDetails has invalid notificationDestination, err=%s", err)
+ }
+
+ return nil
+}
+
+func validateEvent(event CAPIFEvent) error {
+ switch event {
+ case CAPIFEventACCESSCONTROLPOLICYUNAVAILABLE:
+ case CAPIFEventACCESSCONTROLPOLICYUPDATE:
+ case CAPIFEventAPIINVOKERAUTHORIZATIONREVOKED:
+ case CAPIFEventAPIINVOKEROFFBOARDED:
+ case CAPIFEventAPIINVOKERONBOARDED:
+ case CAPIFEventAPIINVOKERUPDATED:
+ case CAPIFEventAPITOPOLOGYHIDINGCREATED:
+ case CAPIFEventAPITOPOLOGYHIDINGREVOKED:
+ case CAPIFEventSERVICEAPIAVAILABLE:
+ case CAPIFEventSERVICEAPIINVOCATIONFAILURE:
+ case CAPIFEventSERVICEAPIINVOCATIONSUCCESS:
+ case CAPIFEventSERVICEAPIUNAVAILABLE:
+ case CAPIFEventSERVICEAPIUPDATE:
+ default:
+ return errors.New("wrong event type")
+ }
+ return nil
+}
--- /dev/null
+// -
+// ========================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 eventsapi
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestValidateEventSubscription(t *testing.T) {
+ subUnderTest := EventSubscription{}
+
+ err := subUnderTest.Validate()
+
+ assert.NotNil(t, err)
+ assert.Contains(t, err.Error(), "required")
+ assert.Contains(t, err.Error(), "events")
+
+ var invalidEventType CAPIFEvent = "invalid"
+ subUnderTest.Events = []CAPIFEvent{invalidEventType}
+ err = subUnderTest.Validate()
+ assert.NotNil(t, err)
+ assert.Contains(t, err.Error(), "invalid")
+ assert.Contains(t, err.Error(), "events")
+
+ subUnderTest.Events = []CAPIFEvent{CAPIFEventAPIINVOKERONBOARDED}
+ err = subUnderTest.Validate()
+ assert.NotNil(t, err)
+ assert.Contains(t, err.Error(), "missing")
+ assert.Contains(t, err.Error(), "notificationDestination")
+
+ subUnderTest.NotificationDestination = "invalid dest"
+ err = subUnderTest.Validate()
+ if assert.Error(t, err) {
+ assert.Contains(t, err.Error(), "invalid")
+ assert.Contains(t, err.Error(), "notificationDestination")
+ }
+
+ subUnderTest.NotificationDestination = "http://golang.cafe/"
+ err = subUnderTest.Validate()
+ assert.Nil(t, err)
+}
if err != nil {
return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
}
+
+ if err := newSubscription.Validate(); err != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
+ }
+
uri := ctx.Request().Host + ctx.Request().URL.String()
subId := es.getSubscriptionId(subscriberId)
es.addSubscription(subId, newSubscription)
}
func (es *EventService) DeleteSubscriberIdSubscriptionsSubscriptionId(ctx echo.Context, subscriberId string, subscriptionId string) error {
- es.lock.Lock()
- defer es.lock.Unlock()
log.Debug(es.subscriptions)
if _, ok := es.subscriptions[subscriptionId]; ok {
- log.Debug("Deleting subscription", subscriptionId)
- delete(es.subscriptions, subscriptionId)
+ es.deleteSubscription(subscriptionId)
}
return ctx.NoContent(http.StatusNoContent)
}
+func (es *EventService) deleteSubscription(subscriptionId string) {
+ log.Debug("Deleting subscription", subscriptionId)
+ es.lock.Lock()
+ defer es.lock.Unlock()
+ delete(es.subscriptions, subscriptionId)
+}
+
func getEventSubscriptionFromRequest(ctx echo.Context) (eventsapi.EventSubscription, error) {
var subscription eventsapi.EventSubscription
err := ctx.Bind(&subscription)
filterIds := getIds(filter)
if filterIds == nil || len(*filterIds) == 0 {
return matchesFilters(eventIds, filters[1:], getIds)
+ } else {
+ return slices.Contains(*getIds(filter), id) && matchesFilters(eventIds, filters[1:], getIds)
}
- return slices.Contains(*getIds(filter), id) && matchesFilters(eventIds, filters[1:], getIds)
}
return true
}
func (es *EventService) addSubscription(subId string, subscription eventsapi.EventSubscription) {
es.lock.Lock()
+ defer es.lock.Unlock()
es.subscriptions[subId] = subscription
- es.lock.Unlock()
}
func (es *EventService) getSubscription(subId string) *eventsapi.EventSubscription {
"bytes"
"encoding/json"
"fmt"
- "io/ioutil"
+ "io"
"net/http"
"os"
"path"
Events: []eventsapi.CAPIFEvent{
eventsapi.CAPIFEventSERVICEAPIAVAILABLE,
},
- NotificationDestination: common29122.Uri("notificationUrl"),
+ NotificationDestination: common29122.Uri("http://golang.cafe/"),
}
serviceUnderTest, requestHandler := getEcho(nil)
subscriberId := "subscriberId"
assert.Equal(t, subscription2, *registeredSub2)
}
+func TestRegisterInvalidSubscription(t *testing.T) {
+ subscription1 := eventsapi.EventSubscription{
+ Events: []eventsapi.CAPIFEvent{eventsapi.CAPIFEventACCESSCONTROLPOLICYUNAVAILABLE},
+ }
+ serviceUnderTest, requestHandler := getEcho(nil)
+ subscriberId := "subscriberId"
+
+ result := testutil.NewRequest().Post("/"+subscriberId+"/subscriptions").WithJsonBody(subscription1).Go(t, requestHandler)
+ assert.Equal(t, http.StatusBadRequest, result.Code())
+ var problemDetails common29122.ProblemDetails
+ err := result.UnmarshalBodyToObject(&problemDetails)
+ assert.NoError(t, err, "error unmarshaling response")
+ badRequest := http.StatusBadRequest
+ assert.Equal(t, &badRequest, problemDetails.Status)
+ assert.Contains(t, *problemDetails.Cause, "missing")
+ assert.Contains(t, *problemDetails.Cause, "notificationDestination")
+ subscriptionId := path.Base(result.Recorder.Header().Get(echo.HeaderLocation))
+ registeredSub := serviceUnderTest.getSubscription(subscriptionId)
+ assert.Nil(t, registeredSub)
+}
+
func TestDeregisterSubscription(t *testing.T) {
subscription := eventsapi.EventSubscription{
Events: []eventsapi.CAPIFEvent{
wg.Done()
return &http.Response{
StatusCode: 200,
- Body: ioutil.NopCloser(bytes.NewBufferString(`OK`)),
+ Body: io.NopCloser(bytes.NewBufferString(`OK`)),
Header: make(http.Header), // Must be set to non-nil value or it panics
}
}
}()
if waitTimeout(&wg, 1*time.Second) {
- t.Error("Not all calls to server were made")
+ t.Error("No event notification was sent")
t.Fail()
}
}
package invokermanagement
import (
+ "fmt"
"net/http"
"path"
- "strconv"
- "strings"
"sync"
"oransc.org/nonrtric/capifcore/internal/eventsapi"
- publishapi "oransc.org/nonrtric/capifcore/internal/publishserviceapi"
"oransc.org/nonrtric/capifcore/internal/common29122"
invokerapi "oransc.org/nonrtric/capifcore/internal/invokermanagementapi"
}
func (im *InvokerManager) GetInvokerApiList(invokerId string) *invokerapi.APIList {
+ var apiList invokerapi.APIList = im.publishRegister.GetAllPublishedServices()
+ im.lock.Lock()
+ defer im.lock.Unlock()
invoker, ok := im.onboardedInvokers[invokerId]
if ok {
- var apiList invokerapi.APIList = im.publishRegister.GetAllPublishedServices()
- im.lock.Lock()
- defer im.lock.Unlock()
invoker.ApiList = &apiList
return &apiList
}
// 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.isInvokerOnboarded(newInvoker); err != nil {
+ return sendCoreError(ctx, http.StatusForbidden, fmt.Sprintf(errMsg, err))
}
- im.lock.Lock()
- defer im.lock.Unlock()
-
- newInvoker.ApiInvokerId = im.getId(newInvoker.ApiInvokerInformation)
- onboardingSecret := "onboarding_secret_"
- if newInvoker.ApiInvokerInformation != nil {
- onboardingSecret = onboardingSecret + strings.ReplaceAll(*newInvoker.ApiInvokerInformation, " ", "_")
- } else {
- onboardingSecret = onboardingSecret + *newInvoker.ApiInvokerId
+ if err := im.validateInvoker(newInvoker, ctx); err != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
}
- newInvoker.OnboardingInformation.OnboardingSecret = &onboardingSecret
- var apiList invokerapi.APIList = im.publishRegister.GetAllPublishedServices()
- newInvoker.ApiList = &apiList
+ im.prepareNewInvoker(&newInvoker)
- im.onboardedInvokers[*newInvoker.ApiInvokerId] = newInvoker
go im.sendEvent(*newInvoker.ApiInvokerId, eventsapi.CAPIFEventAPIINVOKERONBOARDED)
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
return nil
}
-// Deletes an individual API Invoker.
-func (im *InvokerManager) DeleteOnboardedInvokersOnboardingId(ctx echo.Context, onboardingId string) error {
+func (im *InvokerManager) isInvokerOnboarded(newInvoker invokerapi.APIInvokerEnrolmentDetails) error {
+ for _, invoker := range im.onboardedInvokers {
+ if err := invoker.ValidateAlreadyOnboarded(newInvoker); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (im *InvokerManager) prepareNewInvoker(newInvoker *invokerapi.APIInvokerEnrolmentDetails) {
+ var apiList invokerapi.APIList = im.publishRegister.GetAllPublishedServices()
+ newInvoker.ApiList = &apiList
+
im.lock.Lock()
defer im.lock.Unlock()
- delete(im.onboardedInvokers, onboardingId)
+ newInvoker.PrepareNewInvoker()
+
+ im.onboardedInvokers[*newInvoker.ApiInvokerId] = *newInvoker
+}
+
+// Deletes an individual API Invoker.
+func (im *InvokerManager) DeleteOnboardedInvokersOnboardingId(ctx echo.Context, onboardingId string) error {
+ if _, ok := im.onboardedInvokers[onboardingId]; ok {
+ im.deleteInvoker(onboardingId)
+ }
+
go im.sendEvent(onboardingId, eventsapi.CAPIFEventAPIINVOKEROFFBOARDED)
return ctx.NoContent(http.StatusNoContent)
}
+func (im *InvokerManager) deleteInvoker(onboardingId string) {
+ im.lock.Lock()
+ defer im.lock.Unlock()
+ delete(im.onboardedInvokers, onboardingId)
+}
+
// 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))
}
- im.lock.Lock()
- defer im.lock.Unlock()
-
if _, ok := im.onboardedInvokers[onboardingId]; ok {
- im.onboardedInvokers[*invoker.ApiInvokerId] = invoker
+ im.updateInvoker(invoker)
} else {
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
return nil
}
-func (im *InvokerManager) ModifyIndApiInvokeEnrolment(ctx echo.Context, onboardingId string) error {
- return ctx.NoContent(http.StatusNotImplemented)
+func (im *InvokerManager) updateInvoker(invoker invokerapi.APIInvokerEnrolmentDetails) {
+ im.lock.Lock()
+ defer im.lock.Unlock()
+ im.onboardedInvokers[*invoker.ApiInvokerId] = invoker
}
-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")
- }
-
- 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 false, nil
+func (im *InvokerManager) ModifyIndApiInvokeEnrolment(ctx echo.Context, onboardingId string) error {
+ return ctx.NoContent(http.StatusNotImplemented)
}
-func (im *InvokerManager) areAPIsPublished(apis *invokerapi.APIList) bool {
- if apis == nil {
- return true
+func (im *InvokerManager) validateInvoker(invoker invokerapi.APIInvokerEnrolmentDetails, ctx echo.Context) error {
+ if err := invoker.Validate(); err != nil {
+ return err
}
- 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
+ return nil
}
func (im *InvokerManager) sendEvent(invokerId string, eventType eventsapi.CAPIFEvent) {
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])
assert.Equal(t, eventsapi.CAPIFEventAPIINVOKERONBOARDED, invokerEvent.Events)
}
+ // Onboarding the same invoker should result in Forbidden
+ result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler)
+
+ assert.Equal(t, http.StatusForbidden, result.Code())
+ var problemDetails common29122.ProblemDetails
+ err = result.UnmarshalBodyToObject(&problemDetails)
+ assert.NoError(t, err, "error unmarshaling response")
+ assert.Equal(t, http.StatusForbidden, *problemDetails.Status)
+ assert.Contains(t, *problemDetails.Cause, "already onboarded")
+
// Onboard an invoker missing required NotificationDestination, should get 400 with problem details
invalidInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
OnboardingInformation: invokermanagementapi.OnboardingInformation{
- ApiInvokerPublicKey: "key",
+ ApiInvokerPublicKey: "newKey",
},
}
result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(invalidInvoker).Go(t, requestHandler)
assert.Equal(t, http.StatusBadRequest, result.Code())
- var problemDetails common29122.ProblemDetails
err = result.UnmarshalBodyToObject(&problemDetails)
assert.NoError(t, err, "error unmarshaling response")
- badRequest := http.StatusBadRequest
- assert.Equal(t, &badRequest, problemDetails.Status)
+ assert.Equal(t, http.StatusBadRequest, *problemDetails.Status)
assert.Contains(t, *problemDetails.Cause, "missing")
assert.Contains(t, *problemDetails.Cause, "NotificationDestination")
// Onboard an invoker missing required OnboardingInformation.ApiInvokerPublicKey, should get 400 with problem details
invalidInvoker = invokermanagementapi.APIInvokerEnrolmentDetails{
- NotificationDestination: "url",
+ NotificationDestination: "http://golang.cafe/",
}
result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(invalidInvoker).Go(t, requestHandler)
assert.Equal(t, http.StatusBadRequest, result.Code())
err = result.UnmarshalBodyToObject(&problemDetails)
assert.NoError(t, err, "error unmarshaling response")
- assert.Equal(t, &badRequest, problemDetails.Status)
+ assert.Equal(t, http.StatusBadRequest, *problemDetails.Status)
assert.Contains(t, *problemDetails.Cause, "missing")
assert.Contains(t, *problemDetails.Cause, "OnboardingInformation.ApiInvokerPublicKey")
}
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])
invokerId := "invokerId"
invoker := invokermanagementapi.APIInvokerEnrolmentDetails{
ApiInvokerId: &invokerId,
- NotificationDestination: "url",
+ NotificationDestination: "http://golang.cafe/",
OnboardingInformation: invokermanagementapi.OnboardingInformation{
ApiInvokerPublicKey: "key",
},
serviceUnderTest.onboardedInvokers[invokerId] = invoker
// Update the invoker with valid invoker, should return 200 with updated invoker details
- newNotifURL := "newUrl"
+ newNotifURL := "http://golang.org/"
invoker.NotificationDestination = common29122.Uri(newNotifURL)
newPublicKey := "newPublicKey"
invoker.OnboardingInformation.ApiInvokerPublicKey = newPublicKey
var problemDetails common29122.ProblemDetails
err = result.UnmarshalBodyToObject(&problemDetails)
assert.NoError(t, err, "error unmarshaling response")
- badRequest := http.StatusBadRequest
- assert.Equal(t, &badRequest, problemDetails.Status)
+ assert.Equal(t, http.StatusBadRequest, *problemDetails.Status)
assert.Contains(t, *problemDetails.Cause, "missing")
assert.Contains(t, *problemDetails.Cause, "NotificationDestination")
// Update with an invoker missing required OnboardingInformation.ApiInvokerPublicKey, should get 400 with problem details
- invalidInvoker.NotificationDestination = "url"
+ invalidInvoker.NotificationDestination = "http://golang.org/"
invalidInvoker.OnboardingInformation = invokermanagementapi.OnboardingInformation{}
result = testutil.NewRequest().Put("/onboardedInvokers/"+invokerId).WithJsonBody(invalidInvoker).Go(t, requestHandler)
assert.Equal(t, http.StatusBadRequest, result.Code())
err = result.UnmarshalBodyToObject(&problemDetails)
assert.NoError(t, err, "error unmarshaling response")
- assert.Equal(t, &badRequest, problemDetails.Status)
+ assert.Equal(t, http.StatusBadRequest, *problemDetails.Status)
assert.Contains(t, *problemDetails.Cause, "missing")
assert.Contains(t, *problemDetails.Cause, "OnboardingInformation.ApiInvokerPublicKey")
assert.Equal(t, http.StatusBadRequest, result.Code())
err = result.UnmarshalBodyToObject(&problemDetails)
assert.NoError(t, err, "error unmarshaling response")
- assert.Equal(t, &badRequest, problemDetails.Status)
+ assert.Equal(t, http.StatusBadRequest, *problemDetails.Status)
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)
assert.Equal(t, http.StatusNotFound, result.Code())
err = result.UnmarshalBodyToObject(&problemDetails)
assert.NoError(t, err, "error unmarshaling response")
- notFound := http.StatusNotFound
- assert.Equal(t, ¬Found, problemDetails.Status)
+ assert.Equal(t, http.StatusNotFound, *problemDetails.Status)
assert.Contains(t, *problemDetails.Cause, "not been onboarded")
assert.Contains(t, *problemDetails.Cause, "invoker")
}
func getInvoker(invokerInfo string) invokermanagementapi.APIInvokerEnrolmentDetails {
newInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
ApiInvokerInformation: &invokerInfo,
- NotificationDestination: "url",
+ NotificationDestination: "http://golang.cafe/",
OnboardingInformation: invokermanagementapi.OnboardingInformation{
ApiInvokerPublicKey: "key",
},
--- /dev/null
+// -
+// ========================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
+}
--- /dev/null
+// -
+// ========================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)
+}
--- /dev/null
+// -
+// ========================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"
+ "fmt"
+ "net/url"
+)
+
+func (ied *APIInvokerEnrolmentDetails) Validate() error {
+ if ied.NotificationDestination == "" {
+ return errors.New("APIInvokerEnrolmentDetails missing required NotificationDestination")
+ }
+
+ if _, err := url.ParseRequestURI(string(ied.NotificationDestination)); err != nil {
+ return fmt.Errorf("APIInvokerEnrolmentDetails has invalid NotificationDestination, err=%s", err)
+ }
+
+ if ied.OnboardingInformation.ApiInvokerPublicKey == "" {
+ return errors.New("APIInvokerEnrolmentDetails missing required OnboardingInformation.ApiInvokerPublicKey")
+ }
+
+ return nil
+}
+
+func (ied *APIInvokerEnrolmentDetails) ValidateAlreadyOnboarded(otherInvoker APIInvokerEnrolmentDetails) error {
+ if ied.OnboardingInformation.ApiInvokerPublicKey == otherInvoker.OnboardingInformation.ApiInvokerPublicKey {
+ return errors.New("invoker with identical public key already onboarded")
+ }
+ return nil
+}
--- /dev/null
+// -
+// ========================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 = "invalid dest"
+ err = invokerUnderTest.Validate()
+ if assert.Error(t, err) {
+ assert.Contains(t, err.Error(), "invalid")
+ assert.Contains(t, err.Error(), "NotificationDestination")
+ }
+
+ invokerUnderTest.NotificationDestination = "http://golang.cafe/"
+ 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)
+}
+
+func TestValidateAlreadyOnboarded(t *testing.T) {
+ publicKey := "publicKey"
+ invokerUnderTest := APIInvokerEnrolmentDetails{
+ OnboardingInformation: OnboardingInformation{
+ ApiInvokerPublicKey: publicKey,
+ },
+ }
+
+ otherInvoker := APIInvokerEnrolmentDetails{
+ OnboardingInformation: OnboardingInformation{
+ ApiInvokerPublicKey: "otherPublicKey",
+ },
+ }
+ assert.Nil(t, invokerUnderTest.ValidateAlreadyOnboarded(otherInvoker))
+
+ otherInvoker.OnboardingInformation.ApiInvokerPublicKey = publicKey
+ assert.NotNil(t, invokerUnderTest.ValidateAlreadyOnboarded(otherInvoker))
+}
"fmt"
"net/http"
"path"
- "strings"
"sync"
"github.com/labstack/echo/v4"
}
type ProviderManager struct {
- onboardedProviders map[string]provapi.APIProviderEnrolmentDetails
- lock sync.Mutex
+ registeredProviders map[string]provapi.APIProviderEnrolmentDetails
+ lock sync.Mutex
}
func NewProviderManager() *ProviderManager {
return &ProviderManager{
- onboardedProviders: make(map[string]provapi.APIProviderEnrolmentDetails),
+ registeredProviders: make(map[string]provapi.APIProviderEnrolmentDetails),
}
}
func (pm *ProviderManager) IsFunctionRegistered(functionId string) bool {
- registered := false
-out:
- for _, provider := range pm.onboardedProviders {
- for _, registeredFunc := range *provider.ApiProvFuncs {
- if *registeredFunc.ApiProvFuncId == functionId {
- registered = true
- break out
- }
+ for _, provider := range pm.registeredProviders {
+ if provider.IsFunctionRegistered(functionId) {
+ return true
}
}
-
- return registered
+ return false
}
func (pm *ProviderManager) GetAefsForPublisher(apfId string) []string {
- for _, provider := range pm.onboardedProviders {
- for _, registeredFunc := range *provider.ApiProvFuncs {
- if *registeredFunc.ApiProvFuncId == apfId && registeredFunc.ApiProvFuncRole == provapi.ApiProviderFuncRoleAPF {
- return getExposedFuncs(provider.ApiProvFuncs)
- }
+ for _, provider := range pm.registeredProviders {
+ if aefs := provider.GetExposingFunctionIdsForPublisher(apfId); aefs != nil {
+ return aefs
}
}
return nil
}
-func getExposedFuncs(providerFuncs *[]provapi.APIProviderFunctionDetails) []string {
- exposedFuncs := []string{}
- for _, registeredFunc := range *providerFuncs {
- if registeredFunc.ApiProvFuncRole == provapi.ApiProviderFuncRoleAEF {
- exposedFuncs = append(exposedFuncs, *registeredFunc.ApiProvFuncId)
- }
- }
- return exposedFuncs
-}
-
func (pm *ProviderManager) PostRegistrations(ctx echo.Context) error {
var newProvider provapi.APIProviderEnrolmentDetails
- err := ctx.Bind(&newProvider)
- if err != nil {
- return sendCoreError(ctx, http.StatusBadRequest, "Invalid format for provider")
+ errMsg := "Unable to register provider due to %s"
+ if err := ctx.Bind(&newProvider); err != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, "invalid format for provider"))
}
- if newProvider.ApiProvDomInfo == nil || *newProvider.ApiProvDomInfo == "" {
- return sendCoreError(ctx, http.StatusBadRequest, "Provider missing required ApiProvDomInfo")
+ if err := pm.isProviderRegistered(newProvider); err != nil {
+ return sendCoreError(ctx, http.StatusForbidden, fmt.Sprintf(errMsg, err))
}
- pm.lock.Lock()
- defer pm.lock.Unlock()
-
- newProvider.ApiProvDomId = pm.getDomainId(newProvider.ApiProvDomInfo)
+ if err := newProvider.Validate(); err != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
+ }
- pm.registerFunctions(newProvider.ApiProvFuncs)
- pm.onboardedProviders[*newProvider.ApiProvDomId] = newProvider
+ pm.prepareNewProvider(&newProvider)
uri := ctx.Request().Host + ctx.Request().URL.String()
ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, *newProvider.ApiProvDomId))
- err = ctx.JSON(http.StatusCreated, newProvider)
- if err != nil {
+ if err := ctx.JSON(http.StatusCreated, newProvider); err != nil {
// Something really bad happened, tell Echo that our handler failed
return err
}
+ return nil
+}
+func (pm *ProviderManager) isProviderRegistered(newProvider provapi.APIProviderEnrolmentDetails) error {
+ for _, prov := range pm.registeredProviders {
+ if err := prov.ValidateAlreadyRegistered(newProvider); err != nil {
+ return err
+ }
+ }
return nil
}
-func (pm *ProviderManager) DeleteRegistrationsRegistrationId(ctx echo.Context, registrationId string) error {
+func (pm *ProviderManager) prepareNewProvider(newProvider *provapi.APIProviderEnrolmentDetails) {
pm.lock.Lock()
defer pm.lock.Unlock()
- log.Debug(pm.onboardedProviders)
- if _, ok := pm.onboardedProviders[registrationId]; ok {
- log.Debug("Deleting provider", registrationId)
- delete(pm.onboardedProviders, registrationId)
- }
+ newProvider.PrepareNewProvider()
+ pm.registeredProviders[*newProvider.ApiProvDomId] = *newProvider
+}
+func (pm *ProviderManager) DeleteRegistrationsRegistrationId(ctx echo.Context, registrationId string) error {
+ log.Debug(pm.registeredProviders)
+ if _, ok := pm.registeredProviders[registrationId]; ok {
+ pm.deleteProvider(registrationId)
+ }
return ctx.NoContent(http.StatusNoContent)
}
-func (pm *ProviderManager) PutRegistrationsRegistrationId(ctx echo.Context, registrationId string) error {
+func (pm *ProviderManager) deleteProvider(registrationId string) {
+ log.Debug("Deleting provider", registrationId)
pm.lock.Lock()
defer pm.lock.Unlock()
+ delete(pm.registeredProviders, registrationId)
+}
+func (pm *ProviderManager) PutRegistrationsRegistrationId(ctx echo.Context, registrationId string) error {
errMsg := "Unable to update provider due to %s."
registeredProvider, err := pm.checkIfProviderIsRegistered(registrationId, ctx)
if err != nil {
return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
}
- updateDomainInfo(&updatedProvider, registeredProvider)
+ if updatedProvider.Validate() != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
+ }
- registeredProvider.ApiProvFuncs, err = updateFuncs(updatedProvider.ApiProvFuncs, registeredProvider.ApiProvFuncs)
- if err != nil {
+ if err = pm.updateProvider(updatedProvider, registeredProvider); err != nil {
return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
}
- pm.onboardedProviders[*registeredProvider.ApiProvDomId] = *registeredProvider
- err = ctx.JSON(http.StatusOK, *registeredProvider)
- if err != nil {
+ if err = ctx.JSON(http.StatusOK, updatedProvider); err != nil {
// Something really bad happened, tell Echo that our handler failed
return err
}
-
return nil
}
+func (pm *ProviderManager) ModifyIndApiProviderEnrolment(ctx echo.Context, registrationId string) error {
+ return ctx.NoContent(http.StatusNotImplemented)
+}
+
func (pm *ProviderManager) checkIfProviderIsRegistered(registrationId string, ctx echo.Context) (*provapi.APIProviderEnrolmentDetails, error) {
- registeredProvider, ok := pm.onboardedProviders[registrationId]
+ registeredProvider, ok := pm.registeredProviders[registrationId]
if !ok {
return nil, fmt.Errorf("provider not onboarded")
}
return updatedProvider, nil
}
-func updateDomainInfo(updatedProvider, registeredProvider *provapi.APIProviderEnrolmentDetails) {
- if updatedProvider.ApiProvDomInfo != nil {
- registeredProvider.ApiProvDomInfo = updatedProvider.ApiProvDomInfo
- }
-}
-
-func updateFuncs(updatedFuncs, registeredFuncs *[]provapi.APIProviderFunctionDetails) (*[]provapi.APIProviderFunctionDetails, error) {
- addedFuncs := []provapi.APIProviderFunctionDetails{}
- changedFuncs := []provapi.APIProviderFunctionDetails{}
- for _, function := range *updatedFuncs {
- if function.ApiProvFuncId == nil {
- function.ApiProvFuncId = getFuncId(function.ApiProvFuncRole, function.ApiProvFuncInfo)
- addedFuncs = append(addedFuncs, function)
- } else {
- registeredFunction, ok := getApiFunc(*function.ApiProvFuncId, registeredFuncs)
- if !ok {
- return nil, fmt.Errorf("function with ID %s is not registered for the provider", *function.ApiProvFuncId)
- }
- if function.ApiProvFuncInfo != nil {
- registeredFunction.ApiProvFuncInfo = function.ApiProvFuncInfo
- }
- changedFuncs = append(changedFuncs, function)
- }
- }
- modifiedFuncs := append(changedFuncs, addedFuncs...)
- return &modifiedFuncs, nil
-}
-
-func getApiFunc(funcId string, apiFunctions *[]provapi.APIProviderFunctionDetails) (provapi.APIProviderFunctionDetails, bool) {
- for _, function := range *apiFunctions {
- if *function.ApiProvFuncId == funcId {
- return function, true
- }
- }
- return provapi.APIProviderFunctionDetails{}, false
-}
-
-func (pm *ProviderManager) ModifyIndApiProviderEnrolment(ctx echo.Context, registrationId string) error {
- return ctx.NoContent(http.StatusNotImplemented)
-}
-
-func (pm *ProviderManager) registerFunctions(provFuncs *[]provapi.APIProviderFunctionDetails) {
- if provFuncs == nil {
- return
- }
- for i, provFunc := range *provFuncs {
- (*provFuncs)[i].ApiProvFuncId = getFuncId(provFunc.ApiProvFuncRole, provFunc.ApiProvFuncInfo)
- }
-}
-
-func (pm *ProviderManager) getDomainId(domainInfo *string) *string {
- idAsString := "domain_id_" + strings.ReplaceAll(*domainInfo, " ", "_")
- return &idAsString
-}
+func (pm *ProviderManager) updateProvider(updatedProvider provapi.APIProviderEnrolmentDetails, registeredProvider *provapi.APIProviderEnrolmentDetails) error {
+ pm.lock.Lock()
+ defer pm.lock.Unlock()
-func getFuncId(role provapi.ApiProviderFuncRole, funcInfo *string) *string {
- var idPrefix string
- switch role {
- case provapi.ApiProviderFuncRoleAPF:
- idPrefix = "APF_id_"
- case provapi.ApiProviderFuncRoleAMF:
- idPrefix = "AMF_id_"
- case provapi.ApiProviderFuncRoleAEF:
- idPrefix = "AEF_id_"
- default:
- idPrefix = "function_id_"
+ if err := updatedProvider.UpdateFuncs(*registeredProvider); err == nil {
+ pm.registeredProviders[*updatedProvider.ApiProvDomId] = updatedProvider
+ return nil
+ } else {
+ return err
}
- idAsString := idPrefix + strings.ReplaceAll(*funcInfo, " ", "_")
- return &idAsString
}
// This function wraps sending of an error in the Error format, and
assert.Empty(t, resultProvider.FailReason)
assert.Equal(t, "http://example.com/registrations/"+*resultProvider.ApiProvDomId, result.Recorder.Header().Get(echo.HeaderLocation))
assert.True(t, managerUnderTest.IsFunctionRegistered("APF_id_rApp_as_APF"))
+
+ // Register same provider again should result in Forbidden
+ result = testutil.NewRequest().Post("/registrations").WithJsonBody(newProvider).Go(t, requestHandler)
+ var errorObj common29122.ProblemDetails
+ assert.Equal(t, http.StatusForbidden, result.Code())
+ err = result.UnmarshalBodyToObject(&errorObj)
+ assert.NoError(t, err, "error unmarshaling response")
+ assert.Equal(t, http.StatusForbidden, *errorObj.Status)
+ assert.Contains(t, *errorObj.Cause, "already registered")
}
func TestUpdateValidProviderWithNewFunction(t *testing.T) {
(*provider.ApiProvFuncs)[0].ApiProvFuncId = &funcIdAPF
(*provider.ApiProvFuncs)[1].ApiProvFuncId = &funcIdAMF
(*provider.ApiProvFuncs)[2].ApiProvFuncId = &funcIdAEF
- managerUnderTest.onboardedProviders[domainID] = provider
+ managerUnderTest.registeredProviders[domainID] = provider
// Modify the provider
updatedProvider := getProvider()
testFuncs = append(testFuncs, provapi.APIProviderFunctionDetails{
ApiProvFuncInfo: &newFuncInfoAEF,
ApiProvFuncRole: provapi.ApiProviderFuncRoleAEF,
+ RegInfo: provapi.RegistrationInformation{
+ ApiProvPubKey: "key",
+ },
})
updatedProvider.ApiProvFuncs = &testFuncs
(*provider.ApiProvFuncs)[0].ApiProvFuncId = &funcIdAPF
(*provider.ApiProvFuncs)[1].ApiProvFuncId = &funcIdAMF
(*provider.ApiProvFuncs)[2].ApiProvFuncId = &funcIdAEF
- managerUnderTest.onboardedProviders[domainID] = provider
+ managerUnderTest.registeredProviders[domainID] = provider
// Modify the provider
updatedProvider := getProvider()
(*provider.ApiProvFuncs)[0].ApiProvFuncId = &otherId
(*provider.ApiProvFuncs)[1].ApiProvFuncId = &funcIdAMF
(*provider.ApiProvFuncs)[2].ApiProvFuncId = &funcIdAEF
- managerUnderTest.onboardedProviders[domainID] = provider
+ managerUnderTest.registeredProviders[domainID] = provider
// Modify the provider
updatedProvider := getProvider()
assert.Equal(t, http.StatusBadRequest, result.Code())
err := result.UnmarshalBodyToObject(&errorObj)
assert.NoError(t, err, "error unmarshaling response")
+ assert.Equal(t, http.StatusBadRequest, *errorObj.Status)
assert.Contains(t, *errorObj.Cause, funcIdAPF)
assert.Contains(t, *errorObj.Cause, "not registered")
}
provider := getProvider()
provider.ApiProvDomId = &domainID
(*provider.ApiProvFuncs)[0].ApiProvFuncId = &funcIdAPF
- managerUnderTest.onboardedProviders[domainID] = provider
+ managerUnderTest.registeredProviders[domainID] = provider
assert.True(t, managerUnderTest.IsFunctionRegistered(funcIdAPF))
result := testutil.NewRequest().Delete("/registrations/"+domainID).Go(t, requestHandler)
newProvider := provapi.APIProviderEnrolmentDetails{}
- // Register a valid provider
+ // Register an invalid provider
result := testutil.NewRequest().Post("/registrations").WithJsonBody(newProvider).Go(t, requestHandler)
assert.Equal(t, http.StatusBadRequest, result.Code())
var problemDetails common29122.ProblemDetails
err := result.UnmarshalBodyToObject(&problemDetails)
assert.NoError(t, err, "error unmarshaling response")
- badRequest := http.StatusBadRequest
- assert.Equal(t, &badRequest, problemDetails.Status)
- errMsg := "Provider missing required ApiProvDomInfo"
- assert.Equal(t, &errMsg, problemDetails.Cause)
+ assert.Equal(t, http.StatusBadRequest, *problemDetails.Status)
+ assert.Contains(t, *problemDetails.Cause, "missing")
+ assert.Contains(t, *problemDetails.Cause, "regSec")
}
func TestGetExposedFunctionsForPublishingFunction(t *testing.T) {
(*provider.ApiProvFuncs)[0].ApiProvFuncId = &funcIdAPF
(*provider.ApiProvFuncs)[1].ApiProvFuncId = &funcIdAMF
(*provider.ApiProvFuncs)[2].ApiProvFuncId = &funcIdAEF
- managerUnderTest.onboardedProviders[domainID] = provider
- managerUnderTest.onboardedProviders[otherDomainID] = getOtherProvider()
+ managerUnderTest.registeredProviders[domainID] = provider
+ managerUnderTest.registeredProviders[otherDomainID] = getOtherProvider()
exposedFuncs := managerUnderTest.GetAefsForPublisher(funcIdAPF)
assert.Equal(t, 1, len(exposedFuncs))
{
ApiProvFuncInfo: &funcInfoAPF,
ApiProvFuncRole: provapi.ApiProviderFuncRoleAPF,
+ RegInfo: provapi.RegistrationInformation{
+ ApiProvPubKey: "key",
+ },
},
{
ApiProvFuncInfo: &funcInfoAMF,
ApiProvFuncRole: provapi.ApiProviderFuncRoleAMF,
+ RegInfo: provapi.RegistrationInformation{
+ ApiProvPubKey: "key",
+ },
},
{
ApiProvFuncInfo: &funcInfoAEF,
ApiProvFuncRole: provapi.ApiProviderFuncRoleAEF,
+ RegInfo: provapi.RegistrationInformation{
+ ApiProvPubKey: "key",
+ },
},
}
return provapi.APIProviderEnrolmentDetails{
+ RegSec: "sec",
ApiProvDomInfo: &domainInfo,
ApiProvFuncs: &testFuncs,
}
--- /dev/null
+// -
+// ========================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 providermanagementapi
+
+func (ed APIProviderEnrolmentDetails) GetExposingFunctionIdsForPublisher(apfId string) []string {
+ for _, registeredFunc := range *ed.ApiProvFuncs {
+ if *registeredFunc.ApiProvFuncId == apfId && registeredFunc.isProvidingFunction() {
+ return ed.getExposingFunctionIds()
+ }
+ }
+ return nil
+}
+
+func (ed APIProviderEnrolmentDetails) getExposingFunctionIds() []string {
+ exposedFuncs := []string{}
+ for _, registeredFunc := range *ed.ApiProvFuncs {
+ if registeredFunc.isExposingFunction() {
+ exposedFuncs = append(exposedFuncs, *registeredFunc.ApiProvFuncId)
+ }
+ }
+ return exposedFuncs
+}
+
+func (ed APIProviderEnrolmentDetails) IsFunctionRegistered(functionId string) bool {
+ for _, registeredFunc := range *ed.ApiProvFuncs {
+ if *registeredFunc.ApiProvFuncId == functionId {
+ return true
+ }
+ }
+ return false
+}
+
+func (fd APIProviderFunctionDetails) isProvidingFunction() bool {
+ return fd.ApiProvFuncRole == ApiProviderFuncRoleAPF
+}
+
+func (fd APIProviderFunctionDetails) isExposingFunction() bool {
+ return fd.ApiProvFuncRole == ApiProviderFuncRoleAEF
+}
--- /dev/null
+// -
+// ========================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 providermanagementapi
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetExposedFunctionIds(t *testing.T) {
+ providerUnderTest := getProvider()
+
+ exposedFuncs := providerUnderTest.GetExposingFunctionIdsForPublisher(funcIdAPF)
+
+ assert.Len(t, exposedFuncs, 1)
+ assert.Equal(t, funcIdAEF, exposedFuncs[0])
+
+ exposedFuncs = providerUnderTest.GetExposingFunctionIdsForPublisher("anyId")
+
+ assert.Len(t, exposedFuncs, 0)
+}
+
+func TestIsFunctionRegistered(t *testing.T) {
+ providerUnderTest := getProvider()
+
+ registered := providerUnderTest.IsFunctionRegistered(funcIdAPF)
+
+ assert.True(t, registered)
+
+ registered = providerUnderTest.IsFunctionRegistered("anyID")
+
+ assert.False(t, registered)
+}
--- /dev/null
+// -
+// ========================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 providermanagementapi
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/google/uuid"
+)
+
+var uuidFunc = getUUID
+
+func (ed *APIProviderEnrolmentDetails) UpdateFuncs(registeredProvider APIProviderEnrolmentDetails) error {
+ for pos, function := range *ed.ApiProvFuncs {
+ if function.ApiProvFuncId == nil {
+ (*ed.ApiProvFuncs)[pos].ApiProvFuncId = getFuncId(function.ApiProvFuncRole, function.ApiProvFuncInfo)
+ } else {
+ if !registeredProvider.IsFunctionRegistered(*function.ApiProvFuncId) {
+ return fmt.Errorf("function with ID %s is not registered for the provider", *function.ApiProvFuncId)
+ }
+ }
+ }
+ return nil
+}
+
+func (ed *APIProviderEnrolmentDetails) PrepareNewProvider() {
+ ed.ApiProvDomId = ed.getDomainId()
+
+ ed.registerFunctions()
+
+}
+
+func (ed *APIProviderEnrolmentDetails) getDomainId() *string {
+ var idAsString string
+ if ed.ApiProvDomInfo != nil {
+ idAsString = strings.ReplaceAll(*ed.ApiProvDomInfo, " ", "_")
+ } else {
+ idAsString = uuidFunc()
+ }
+ newId := "domain_id_" + idAsString
+ return &newId
+}
+
+func (ed *APIProviderEnrolmentDetails) registerFunctions() {
+ if ed.ApiProvFuncs == nil {
+ return
+ }
+ for i, provFunc := range *ed.ApiProvFuncs {
+ (*ed.ApiProvFuncs)[i].ApiProvFuncId = getFuncId(provFunc.ApiProvFuncRole, provFunc.ApiProvFuncInfo)
+ }
+}
+
+func getFuncId(role ApiProviderFuncRole, funcInfo *string) *string {
+ var idPrefix string
+ switch role {
+ case ApiProviderFuncRoleAPF:
+ idPrefix = "APF_id_"
+ case ApiProviderFuncRoleAMF:
+ idPrefix = "AMF_id_"
+ case ApiProviderFuncRoleAEF:
+ idPrefix = "AEF_id_"
+ }
+ var id string
+ if funcInfo != nil {
+ id = strings.ReplaceAll(*funcInfo, " ", "_")
+ } else {
+ id = uuidFunc()
+ }
+ idAsString := idPrefix + id
+ return &idAsString
+}
+
+func getUUID() string {
+ return uuid.NewString()
+}
--- /dev/null
+// -
+// ========================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 providermanagementapi
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPrepareNewProvider(t *testing.T) {
+ domainInfo := "domain info"
+ funcInfo := "func info"
+ providerUnderTest := APIProviderEnrolmentDetails{
+ ApiProvDomInfo: &domainInfo,
+ ApiProvFuncs: &[]APIProviderFunctionDetails{
+ {
+ ApiProvFuncRole: ApiProviderFuncRoleAPF,
+ ApiProvFuncInfo: &funcInfo,
+ },
+ {
+ ApiProvFuncRole: ApiProviderFuncRoleAEF,
+ },
+ },
+ }
+ uuidFunc = func() string {
+ return "1"
+ }
+
+ providerUnderTest.PrepareNewProvider()
+
+ assert.Equal(t, "domain_id_domain_info", *providerUnderTest.ApiProvDomId)
+ assert.Equal(t, "APF_id_func_info", *(*providerUnderTest.ApiProvFuncs)[0].ApiProvFuncId)
+ assert.Equal(t, "AEF_id_1", *(*providerUnderTest.ApiProvFuncs)[1].ApiProvFuncId)
+
+ providerUnderTest = APIProviderEnrolmentDetails{}
+
+ providerUnderTest.PrepareNewProvider()
+
+ assert.Equal(t, "domain_id_1", *providerUnderTest.ApiProvDomId)
+}
+
+func TestUpdateFuncs(t *testing.T) {
+ registeredProvider := getProvider()
+
+ funcInfo := "func info"
+ updatedFuncs := []APIProviderFunctionDetails{
+ (*registeredProvider.ApiProvFuncs)[0],
+ (*registeredProvider.ApiProvFuncs)[2],
+ {
+ ApiProvFuncRole: ApiProviderFuncRoleAEF,
+ ApiProvFuncInfo: &funcInfo,
+ },
+ }
+ providerUnderTest := APIProviderEnrolmentDetails{
+ ApiProvFuncs: &updatedFuncs,
+ }
+ err := providerUnderTest.UpdateFuncs(registeredProvider)
+
+ assert.Nil(t, err)
+ assert.Len(t, *providerUnderTest.ApiProvFuncs, 3)
+ assert.Equal(t, funcIdAPF, *(*providerUnderTest.ApiProvFuncs)[0].ApiProvFuncId)
+ assert.Equal(t, funcIdAEF, *(*providerUnderTest.ApiProvFuncs)[1].ApiProvFuncId)
+ assert.Equal(t, "AEF_id_func_info", *(*providerUnderTest.ApiProvFuncs)[2].ApiProvFuncId)
+}
--- /dev/null
+// -
+// ========================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 providermanagementapi
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+)
+
+func (ri RegistrationInformation) Validate() error {
+ if len(strings.TrimSpace(ri.ApiProvPubKey)) == 0 {
+ return errors.New("RegistrationInformation missing required apiProvPubKey")
+ }
+ return nil
+}
+
+func (fd APIProviderFunctionDetails) Validate() error {
+ if len(strings.TrimSpace(string(fd.ApiProvFuncRole))) == 0 {
+ return errors.New("APIProviderFunctionDetails missing required apiProvFuncRole")
+ }
+ switch role := fd.ApiProvFuncRole; role {
+ case ApiProviderFuncRoleAEF:
+ case ApiProviderFuncRoleAPF:
+ case ApiProviderFuncRoleAMF:
+ default:
+ return errors.New("APIProviderFunctionDetails has invalid apiProvFuncRole")
+ }
+
+ return fd.RegInfo.Validate()
+}
+
+func (pd APIProviderEnrolmentDetails) Validate() error {
+ if len(strings.TrimSpace(pd.RegSec)) == 0 {
+ return errors.New("APIProviderEnrolmentDetails missing required regSec")
+ }
+ if pd.ApiProvFuncs != nil {
+ return pd.validateFunctions()
+ }
+ return nil
+}
+
+func (pd APIProviderEnrolmentDetails) validateFunctions() error {
+ for _, function := range *pd.ApiProvFuncs {
+ err := function.Validate()
+ if err != nil {
+ return fmt.Errorf("apiProvFuncs contains invalid function: %s", err)
+ }
+ }
+ return nil
+}
+
+func (pd APIProviderEnrolmentDetails) ValidateAlreadyRegistered(otherProvider APIProviderEnrolmentDetails) error {
+ if pd.RegSec == otherProvider.RegSec {
+ return errors.New("provider with identical regSec already registered")
+ }
+ return nil
+}
--- /dev/null
+// -
+// ========================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 providermanagementapi
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+var (
+ domainID = "domain_id_rApp_domain"
+ otherDomainID = "domain_id_other_domain"
+ domainInfo = "rApp domain"
+ funcInfoAPF = "rApp as APF"
+ funcIdAPF = "APF_id_rApp_as_APF"
+ funcInfoAMF = "rApp as AMF"
+ funcIdAMF = "AMF_id_rApp_as_AMF"
+ funcInfoAEF = "rApp as AEF"
+ funcIdAEF = "AEF_id_rApp_as_AEF"
+)
+
+func TestValidateRegistrationInformation(t *testing.T) {
+ regInfoUnderTest := RegistrationInformation{}
+ err := regInfoUnderTest.Validate()
+ if assert.Error(t, err) {
+ assert.Contains(t, err.Error(), "missing")
+ assert.Contains(t, err.Error(), "apiProvPubKey")
+ }
+
+ regInfoUnderTest.ApiProvPubKey = "key"
+ err = regInfoUnderTest.Validate()
+ assert.Nil(t, err)
+}
+
+func TestValidateAPIProviderFunctionDetails(t *testing.T) {
+ funcDetailsUnderTest := APIProviderFunctionDetails{}
+ err := funcDetailsUnderTest.Validate()
+ if assert.Error(t, err) {
+ assert.Contains(t, err.Error(), "missing")
+ assert.Contains(t, err.Error(), "apiProvFuncRole")
+ }
+
+ var invalidFuncRole ApiProviderFuncRole = "invalid"
+ funcDetailsUnderTest.ApiProvFuncRole = invalidFuncRole
+ err = funcDetailsUnderTest.Validate()
+ if assert.Error(t, err) {
+ assert.Contains(t, err.Error(), "invalid")
+ assert.Contains(t, err.Error(), "apiProvFuncRole")
+ }
+
+ funcDetailsUnderTest.ApiProvFuncRole = ApiProviderFuncRoleAEF
+ err = funcDetailsUnderTest.Validate()
+ if assert.Error(t, err) {
+ assert.Contains(t, err.Error(), "missing")
+ assert.Contains(t, err.Error(), "apiProvPubKey")
+ }
+
+ funcDetailsUnderTest.RegInfo = RegistrationInformation{
+ ApiProvPubKey: "key",
+ }
+ assert.Nil(t, funcDetailsUnderTest.Validate())
+}
+
+func TestValidateAPIProviderEnrolmentDetails(t *testing.T) {
+ providerDetailsUnderTest := APIProviderEnrolmentDetails{}
+ err := providerDetailsUnderTest.Validate()
+ if assert.Error(t, err) {
+ assert.Contains(t, err.Error(), "missing")
+ assert.Contains(t, err.Error(), "regSec")
+ }
+
+ providerDetailsUnderTest.RegSec = "sec"
+ funcs := []APIProviderFunctionDetails{{}}
+ providerDetailsUnderTest.ApiProvFuncs = &funcs
+ err = providerDetailsUnderTest.Validate()
+ if assert.Error(t, err) {
+ assert.Contains(t, err.Error(), "apiProvFuncs")
+ assert.Contains(t, err.Error(), "contains invalid")
+ }
+
+ (*providerDetailsUnderTest.ApiProvFuncs)[0] = APIProviderFunctionDetails{
+ ApiProvFuncRole: ApiProviderFuncRoleAEF,
+ RegInfo: RegistrationInformation{
+ ApiProvPubKey: "key",
+ },
+ }
+ assert.Nil(t, providerDetailsUnderTest.Validate())
+}
+
+func TestValidateAlreadyRegistered(t *testing.T) {
+ regSec := "regSec"
+ providerUnderTest := APIProviderEnrolmentDetails{
+ RegSec: regSec,
+ }
+
+ otherProvider := APIProviderEnrolmentDetails{
+ RegSec: "otherRegSec",
+ }
+ assert.Nil(t, providerUnderTest.ValidateAlreadyRegistered(otherProvider))
+
+ otherProvider.RegSec = regSec
+ assert.NotNil(t, providerUnderTest.ValidateAlreadyRegistered(otherProvider))
+}
+
+func getProvider() APIProviderEnrolmentDetails {
+ testFuncs := []APIProviderFunctionDetails{
+ {
+ ApiProvFuncId: &funcIdAPF,
+ ApiProvFuncInfo: &funcInfoAPF,
+ ApiProvFuncRole: ApiProviderFuncRoleAPF,
+ },
+ {
+ ApiProvFuncId: &funcIdAMF,
+ ApiProvFuncInfo: &funcInfoAMF,
+ ApiProvFuncRole: ApiProviderFuncRoleAMF,
+ },
+ {
+ ApiProvFuncId: &funcIdAEF,
+ ApiProvFuncInfo: &funcInfoAEF,
+ ApiProvFuncRole: ApiProviderFuncRoleAEF,
+ },
+ }
+ return APIProviderEnrolmentDetails{
+ ApiProvDomId: &domainID,
+ ApiProvDomInfo: &domainInfo,
+ ApiProvFuncs: &testFuncs,
+ }
+
+}
mock.Mock
}
-// AreAPIsPublished provides a mock function with given fields: serviceDescriptions
-func (_m *PublishRegister) AreAPIsPublished(serviceDescriptions *[]publishserviceapi.ServiceAPIDescription) bool {
- ret := _m.Called(serviceDescriptions)
-
- var r0 bool
- if rf, ok := ret.Get(0).(func(*[]publishserviceapi.ServiceAPIDescription) bool); ok {
- r0 = rf(serviceDescriptions)
- } else {
- r0 = ret.Get(0).(bool)
- }
-
- return r0
-}
-
// GetAllPublishedServices provides a mock function with given fields:
func (_m *PublishRegister) GetAllPublishedServices() []publishserviceapi.ServiceAPIDescription {
ret := _m.Called()
//go:generate mockery --name PublishRegister
type PublishRegister interface {
- // Checks if the provided APIs are published.
- // Returns true if all provided APIs have been published, false otherwise.
- AreAPIsPublished(serviceDescriptions *[]publishapi.ServiceAPIDescription) bool
// Checks if the provided API is published.
// Returns true if the provided API has been published, false otherwise.
IsAPIPublished(aefId, path string) bool
}
}
-func (ps *PublishService) AreAPIsPublished(serviceDescriptions *[]publishapi.ServiceAPIDescription) bool {
-
- if serviceDescriptions != nil {
- registeredApis := ps.getAllAefIds()
- return checkNewDescriptions(*serviceDescriptions, registeredApis)
- }
- return true
-}
-
func (ps *PublishService) getAllAefIds() []string {
ps.lock.Lock()
defer ps.lock.Unlock()
allIds := []string{}
for _, descriptions := range ps.publishedServices {
for _, description := range descriptions {
- allIds = append(allIds, getIdsFromDescription(description)...)
- }
- }
- return allIds
-}
-
-func getIdsFromDescription(description publishapi.ServiceAPIDescription) []string {
- allIds := []string{}
- if description.AefProfiles != nil {
- for _, aefProfile := range *description.AefProfiles {
- allIds = append(allIds, aefProfile.AefId)
+ allIds = append(allIds, description.GetAefIds()...)
}
}
return allIds
}
-func checkNewDescriptions(newDescriptions []publishapi.ServiceAPIDescription, registeredAefIds []string) bool {
- registered := true
- for _, newApi := range newDescriptions {
- if !checkProfiles(newApi.AefProfiles, registeredAefIds) {
- registered = false
- break
- }
- }
- return registered
-}
-
-func checkProfiles(newProfiles *[]publishapi.AefProfile, registeredAefIds []string) bool {
- allRegistered := true
- if newProfiles != nil {
- for _, profile := range *newProfiles {
- if !slices.Contains(registeredAefIds, profile.AefId) {
- allRegistered = false
- break
- }
- }
- }
- return allRegistered
-}
-
func (ps *PublishService) IsAPIPublished(aefId, path string) bool {
return slices.Contains(ps.getAllAefIds(), aefId)
}
// Publish a new API.
func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) error {
var newServiceAPIDescription publishapi.ServiceAPIDescription
+ errorMsg := "Unable to publish the service due to %s "
err := ctx.Bind(&newServiceAPIDescription)
if err != nil {
- return sendCoreError(ctx, http.StatusBadRequest, "Invalid format for service "+apfId)
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errorMsg, "invalid format for service "+apfId))
+ }
+
+ if err := ps.isServicePublished(newServiceAPIDescription); err != nil {
+ return sendCoreError(ctx, http.StatusForbidden, fmt.Sprintf(errorMsg, err))
}
+ if err := newServiceAPIDescription.Validate(); err != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errorMsg, err))
+ }
ps.lock.Lock()
defer ps.lock.Unlock()
registeredFuncs := ps.serviceRegister.GetAefsForPublisher(apfId)
for _, profile := range *newServiceAPIDescription.AefProfiles {
if !slices.Contains(registeredFuncs, profile.AefId) {
- return sendCoreError(ctx, http.StatusNotFound, fmt.Sprintf("Function %s not registered", profile.AefId))
+ return sendCoreError(ctx, http.StatusNotFound, fmt.Sprintf(errorMsg, fmt.Sprintf("function %s not registered", profile.AefId)))
}
}
- newId := "api_id_" + newServiceAPIDescription.ApiName
- newServiceAPIDescription.ApiId = &newId
+ newServiceAPIDescription.PrepareNewService()
shouldReturn, returnValue := ps.installHelmChart(newServiceAPIDescription, ctx)
if shouldReturn {
return nil
}
+func (ps *PublishService) isServicePublished(newService publishapi.ServiceAPIDescription) error {
+ for _, services := range ps.publishedServices {
+ for _, service := range services {
+ if err := service.ValidateAlreadyPublished(newService); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
func (ps *PublishService) installHelmChart(newServiceAPIDescription publishapi.ServiceAPIDescription, ctx echo.Context) (bool, error) {
info := strings.Split(*newServiceAPIDescription.Description, ",")
if len(info) == 5 {
log.Debug("Deleted service: ", serviceApiId)
}
ps.lock.Lock()
- defer ps.lock.Unlock()
ps.publishedServices[string(apfId)] = removeServiceDescription(pos, serviceDescriptions)
+ ps.lock.Unlock()
go ps.sendEvent(*description, eventsapi.CAPIFEventSERVICEAPIUNAVAILABLE)
}
}
// Retrieve a published service API.
func (ps *PublishService) GetApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
ps.lock.Lock()
- defer ps.lock.Unlock()
-
serviceDescriptions, ok := ps.publishedServices[apfId]
+ ps.lock.Unlock()
+
if ok {
_, serviceDescription := getServiceDescription(serviceApiId, serviceDescriptions)
if serviceDescription == nil {
// Update a published service API.
func (ps *PublishService) PutApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
- pos, publishedService, shouldReturn, returnValue := ps.checkIfServiceIsPublished(apfId, serviceApiId, ctx)
- if shouldReturn {
- return returnValue
- }
-
- updatedServiceDescription, shouldReturn, returnValue := getServiceFromRequest(ctx)
- if shouldReturn {
- return returnValue
+ ps.lock.Lock()
+ defer ps.lock.Unlock()
+ errMsg := "Unable to update service due to %s."
+ pos, publishedService, err := ps.checkIfServiceIsPublished(apfId, serviceApiId, ctx)
+ if err != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
}
-
- if updatedServiceDescription.Description != nil {
- ps.lock.Lock()
- defer ps.lock.Unlock()
-
- publishedService.Description = updatedServiceDescription.Description
- ps.publishedServices[apfId][pos] = publishedService
- go ps.sendEvent(publishedService, eventsapi.CAPIFEventSERVICEAPIUPDATE)
+ updatedServiceDescription, err := getServiceFromRequest(ctx)
+ if err != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
}
-
- pos, shouldReturn, returnValue = ps.updateProfiles(pos, apfId, updatedServiceDescription, publishedService, ctx)
- if shouldReturn {
- return returnValue
+ err = ps.checkProfilesRegistered(apfId, *updatedServiceDescription.AefProfiles)
+ if err != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
}
-
- err := ctx.JSON(http.StatusOK, ps.publishedServices[apfId][pos])
+ ps.updateDescription(pos, apfId, &updatedServiceDescription, &publishedService)
+ publishedService.AefProfiles = updatedServiceDescription.AefProfiles
+ ps.publishedServices[apfId][pos] = publishedService
+ err = ctx.JSON(http.StatusOK, publishedService)
if err != nil {
// Something really bad happened, tell Echo that our handler failed
return err
}
return nil
}
-
-func (ps *PublishService) checkIfServiceIsPublished(apfId string, serviceApiId string, ctx echo.Context) (int, publishapi.ServiceAPIDescription, bool, error) {
+func (ps *PublishService) checkIfServiceIsPublished(apfId string, serviceApiId string, ctx echo.Context) (int, publishapi.ServiceAPIDescription, error) {
publishedServices, ok := ps.publishedServices[apfId]
if !ok {
- return 0, publishapi.ServiceAPIDescription{}, true, sendCoreError(ctx, http.StatusBadRequest, "Service must be published before updating it")
+ return 0, publishapi.ServiceAPIDescription{}, fmt.Errorf("service must be published before updating it")
} else {
for pos, description := range publishedServices {
if *description.ApiId == serviceApiId {
- return pos, description, false, nil
-
+ return pos, description, nil
}
-
}
-
}
- return 0, publishapi.ServiceAPIDescription{}, true, sendCoreError(ctx, http.StatusBadRequest, "Service must be published before updating it")
+ return 0, publishapi.ServiceAPIDescription{}, fmt.Errorf("service must be published before updating it")
}
-
-func getServiceFromRequest(ctx echo.Context) (publishapi.ServiceAPIDescription, bool, error) {
+func getServiceFromRequest(ctx echo.Context) (publishapi.ServiceAPIDescription, error) {
var updatedServiceDescription publishapi.ServiceAPIDescription
err := ctx.Bind(&updatedServiceDescription)
if err != nil {
- return publishapi.ServiceAPIDescription{}, true, sendCoreError(ctx, http.StatusBadRequest, "Invalid format for service")
+ return publishapi.ServiceAPIDescription{}, fmt.Errorf("invalid format for service")
}
- return updatedServiceDescription, false, nil
+ return updatedServiceDescription, nil
}
-
-func (ps *PublishService) updateProfiles(pos int, apfId string, updatedServiceDescription publishapi.ServiceAPIDescription, publishedService publishapi.ServiceAPIDescription, ctx echo.Context) (int, bool, error) {
- registeredFuncs := ps.serviceRegister.GetAefsForPublisher(apfId)
- for _, profile := range *updatedServiceDescription.AefProfiles {
- if !slices.Contains(registeredFuncs, profile.AefId) {
- return 0, false, sendCoreError(ctx, http.StatusNotFound, fmt.Sprintf("Function %s not registered", profile.AefId))
- }
- if ps.checkIfProfileIsNew(profile.AefId, *publishedService.AefProfiles) {
-
- publishedService.AefProfiles = ps.addProfile(profile, publishedService)
- ps.publishedServices[apfId][pos] = publishedService
-
- } else {
- pos, shouldReturn, returnValue := ps.updateProfile(profile, publishedService, ctx)
- if shouldReturn {
- return pos, true, returnValue
- }
- }
-
+func (ps *PublishService) updateDescription(pos int, apfId string, updatedServiceDescription, publishedService *publishapi.ServiceAPIDescription) {
+ if updatedServiceDescription.Description != nil {
+ publishedService.Description = updatedServiceDescription.Description
+ go ps.sendEvent(*publishedService, eventsapi.CAPIFEventSERVICEAPIUPDATE)
}
- return 0, false, nil
}
func (ps *PublishService) sendEvent(service publishapi.ServiceAPIDescription, eventType eventsapi.CAPIFEvent) {
ps.eventChannel <- event
}
-func (ps *PublishService) checkIfProfileIsNew(aefId string, publishedPofiles []publishapi.AefProfile) bool {
- for _, profile := range publishedPofiles {
- if profile.AefId == aefId {
- return false
- }
- }
- return true
-}
-func (ps *PublishService) addProfile(profile publishapi.AefProfile, publishedService publishapi.ServiceAPIDescription) *[]publishapi.AefProfile {
- registeredProfiles := *publishedService.AefProfiles
- newProfiles := append(registeredProfiles, profile)
- publishedService.AefProfiles = &newProfiles
- return &newProfiles
-
-}
-
-func (*PublishService) updateProfile(profile publishapi.AefProfile, publishedService publishapi.ServiceAPIDescription, ctx echo.Context) (int, bool, error) {
- pos, registeredProfile, err := getProfile(profile.AefId, publishedService.AefProfiles)
- if err != nil {
- return pos, true, sendCoreError(ctx, http.StatusBadRequest, "Unable to update service due to: "+err.Error())
- }
- if profile.DomainName != nil {
- registeredProfile.DomainName = profile.DomainName
- (*publishedService.AefProfiles)[pos] = registeredProfile
- }
- return -1, false, nil
-}
-
-func getProfile(profileId string, apiProfiles *[]publishapi.AefProfile) (int, publishapi.AefProfile, error) {
- for pos, profile := range *apiProfiles {
- if profile.AefId == profileId {
- return pos, profile, nil
+func (ps *PublishService) checkProfilesRegistered(apfId string, updatedProfiles []publishapi.AefProfile) error {
+ registeredFuncs := ps.serviceRegister.GetAefsForPublisher(apfId)
+ for _, profile := range updatedProfiles {
+ if !slices.Contains(registeredFuncs, profile.AefId) {
+ return fmt.Errorf("function %s not registered", profile.AefId)
}
}
- return 0, publishapi.AefProfile{}, fmt.Errorf("profile with ID %s is not registered for the service", profileId)
+ return nil
}
// This function wraps sending of an error in the Error format, and
)
func TestPublishUnpublishService(t *testing.T) {
+
apfId := "apfId"
aefId := "aefId"
serviceRegisterMock := serviceMocks.ServiceRegister{}
err := result.UnmarshalBodyToObject(&resultService)
assert.NoError(t, err, "error unmarshaling response")
newApiId := "api_id_" + apiName
- assert.Equal(t, *resultService.ApiId, newApiId)
+ assert.Equal(t, newApiId, *resultService.ApiId)
assert.Equal(t, "http://example.com/"+apfId+"/service-apis/"+*resultService.ApiId, result.Recorder.Header().Get(echo.HeaderLocation))
newServiceDescription.ApiId = &newApiId
- wantedAPILIst := []publishapi.ServiceAPIDescription{newServiceDescription}
- assert.True(t, serviceUnderTest.AreAPIsPublished(&wantedAPILIst))
assert.True(t, serviceUnderTest.IsAPIPublished(aefId, apiName))
serviceRegisterMock.AssertCalled(t, "GetAefsForPublisher", apfId)
helmManagerMock.AssertCalled(t, "InstallHelmChart", namespace, repoName, chartName, releaseName)
assert.NoError(t, err, "error unmarshaling response")
assert.Equal(t, *resultService.ApiId, newApiId)
+ // Publish the same service again should result in Forbidden
+ result = testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
+
+ assert.Equal(t, http.StatusForbidden, result.Code())
+ var resultError common29122.ProblemDetails
+ err = result.UnmarshalBodyToObject(&resultError)
+ assert.NoError(t, err, "error unmarshaling response")
+ assert.Contains(t, *resultError.Cause, "already published")
+ assert.Equal(t, http.StatusForbidden, *resultError.Status)
+
// Delete the service
helmManagerMock.On("UninstallHelmChart", mock.Anything, mock.Anything).Return(nil)
+
result = testutil.NewRequest().Delete("/"+apfId+"/service-apis/"+newApiId).Go(t, requestHandler)
assert.Equal(t, http.StatusNoContent, result.Code())
assert.NoError(t, err, "error unmarshaling response")
assert.Contains(t, *resultError.Cause, aefId)
assert.Contains(t, *resultError.Cause, "not registered")
- notFound := http.StatusNotFound
- assert.Equal(t, ¬Found, resultError.Status)
+ assert.Equal(t, http.StatusNotFound, *resultError.Status)
}
func TestGetServices(t *testing.T) {
assert.Equal(t, newDescription, *resultService.Description)
assert.Equal(t, newDomainName, *(*resultService.AefProfiles)[0].DomainName)
assert.Equal(t, "aefIdNew", (*resultService.AefProfiles)[1].AefId)
+ assert.True(t, serviceUnderTest.IsAPIPublished("aefIdNew", "path"))
if publishEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
assert.Fail(t, "No event sent")
}
}
+func TestUpdateValidServiceWithDeletedFunction(t *testing.T) {
+ apfId := "apfId"
+ serviceApiId := "serviceApiId"
+ aefId := "aefId"
+ apiName := "apiName"
+ description := "description"
+
+ serviceRegisterMock := serviceMocks.ServiceRegister{}
+ serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId", "aefIdNew"})
+ helmManagerMock := helmMocks.HelmManager{}
+ helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
+ serviceUnderTest, _, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
+
+ serviceDescription := getServiceAPIDescription(aefId, apiName, description)
+ serviceDescription.ApiId = &serviceApiId
+ (*serviceDescription.AefProfiles)[0].AefId = aefId
+
+ newProfileDomain := "new profile Domain name"
+ var protocol publishapi.Protocol = "HTTP_1_1"
+ test := make([]publishapi.AefProfile, 1)
+ test = *serviceDescription.AefProfiles
+ test = append(test, publishapi.AefProfile{
+
+ AefId: "aefIdNew",
+ DomainName: &newProfileDomain,
+ Protocol: &protocol,
+ Versions: []publishapi.Version{
+ {
+ ApiVersion: "v1",
+ Resources: &[]publishapi.Resource{
+ {
+ CommType: "REQUEST_RESPONSE",
+ Operations: &[]publishapi.Operation{
+ "POST",
+ },
+ ResourceName: "app",
+ Uri: "app",
+ },
+ },
+ },
+ },
+ },
+ )
+ serviceDescription.AefProfiles = &test
+ serviceUnderTest.publishedServices[apfId] = []publishapi.ServiceAPIDescription{serviceDescription}
+
+ //Modify the service
+ updatedServiceDescription := getServiceAPIDescription(aefId, apiName, description)
+ updatedServiceDescription.ApiId = &serviceApiId
+ test1 := make([]publishapi.AefProfile, 1)
+ test1 = *updatedServiceDescription.AefProfiles
+ test1 = append(test1, publishapi.AefProfile{
+
+ AefId: "aefIdNew",
+ DomainName: &newProfileDomain,
+ Protocol: &protocol,
+ Versions: []publishapi.Version{
+ {
+ ApiVersion: "v1",
+ Resources: &[]publishapi.Resource{
+ {
+ CommType: "REQUEST_RESPONSE",
+ Operations: &[]publishapi.Operation{
+ "POST",
+ },
+ ResourceName: "app",
+ Uri: "app",
+ },
+ },
+ },
+ },
+ },
+ )
+ updatedServiceDescription.AefProfiles = &test1
+ testFunc := []publishapi.AefProfile{
+ (*updatedServiceDescription.AefProfiles)[1],
+ }
+
+ updatedServiceDescription.AefProfiles = &testFunc
+ result := testutil.NewRequest().Put("/"+apfId+"/service-apis/"+serviceApiId).WithJsonBody(updatedServiceDescription).Go(t, requestHandler)
+ var resultService publishapi.ServiceAPIDescription
+ assert.Equal(t, http.StatusOK, result.Code())
+ err := result.UnmarshalBodyToObject(&resultService)
+ assert.NoError(t, err, "error unmarshaling response")
+ assert.Len(t, (*resultService.AefProfiles), 1)
+ assert.False(t, serviceUnderTest.IsAPIPublished("aefId", "path"))
+
+}
+
+func TestPublishInvalidService(t *testing.T) {
+ _, _, requestHandler := getEcho(nil, nil)
+ newServiceDescription := getServiceAPIDescription("aefId", " ", "description")
+
+ // Publish a service
+ result := testutil.NewRequest().Post("/apfId/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
+
+ assert.Equal(t, http.StatusBadRequest, result.Code())
+ var resultError common29122.ProblemDetails
+ err := result.UnmarshalBodyToObject(&resultError)
+ assert.NoError(t, err, "error unmarshaling response")
+ assert.Contains(t, *resultError.Cause, "missing")
+ assert.Contains(t, *resultError.Cause, "apiName")
+ assert.Equal(t, http.StatusBadRequest, *resultError.Status)
+
+}
func getEcho(serviceRegister providermanagement.ServiceRegister, helmManager helmmanagement.HelmManager) (*PublishService, chan eventsapi.EventNotification, *echo.Echo) {
swagger, err := publishapi.GetSwagger()
if err != nil {
--- /dev/null
+// -
+//
+// ========================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 publishserviceapi
+
+func (sd ServiceAPIDescription) GetAefIds() []string {
+ allIds := []string{}
+ if sd.AefProfiles != nil {
+ for _, aefProfile := range *sd.AefProfiles {
+ allIds = append(allIds, aefProfile.AefId)
+ }
+ }
+ return allIds
+}
--- /dev/null
+// -
+// ========================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 publishserviceapi
+
+func (sd *ServiceAPIDescription) PrepareNewService() {
+ apiName := "api_id_" + sd.ApiName
+ sd.ApiId = &apiName
+}
--- /dev/null
+// -
+//
+// ========================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 publishserviceapi
+
+import (
+ "errors"
+ //"fmt"
+ "strings"
+)
+
+func (sd ServiceAPIDescription) Validate() error {
+ if len(strings.TrimSpace(sd.ApiName)) == 0 {
+ return errors.New("ServiceAPIDescription missing required apiName")
+ }
+ return nil
+}
+
+func (sd ServiceAPIDescription) ValidateAlreadyPublished(otherService ServiceAPIDescription) error {
+ if sd.ApiName == otherService.ApiName {
+ return errors.New("service with identical apiName is already published")
+ }
+ return nil
+}
--- /dev/null
+// -
+//
+// ========================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 publishserviceapi
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestValidate(t *testing.T) {
+ serviceDescriptionUnderTest := ServiceAPIDescription{}
+ err := serviceDescriptionUnderTest.Validate()
+ if assert.Error(t, err) {
+ assert.Contains(t, err.Error(), "missing")
+ assert.Contains(t, err.Error(), "apiName")
+ }
+
+ serviceDescriptionUnderTest.ApiName = "apiName"
+ err = serviceDescriptionUnderTest.Validate()
+ assert.Nil(t, err)
+
+}
+
+func TestValidateAlreadyPublished(t *testing.T) {
+ apiName := "apiName"
+ serviceUnderTest := ServiceAPIDescription{
+ ApiName: apiName,
+ }
+
+ otherService := ServiceAPIDescription{
+ ApiName: "otherApiName",
+ }
+ assert.Nil(t, serviceUnderTest.ValidateAlreadyPublished(otherService))
+
+ otherService.ApiName = apiName
+ assert.NotNil(t, serviceUnderTest.ValidateAlreadyPublished(otherService))
+}
func main() {
var port = flag.Int("port", 8090, "Port for CAPIF Core Function HTTP server")
+ var secPort = flag.Int("secPort", 4433, "Port for CAPIF Core Function HTTPS server")
flag.StringVar(&url, "chartMuseumUrl", "", "ChartMuseum URL")
flag.StringVar(&repoName, "repoName", "capifcore", "Repository name")
var logLevelStr = flag.String("loglevel", "Info", "Log level")
+ var certPath = flag.String("certPath", "certs/cert.pem", "Path for server certificate")
+ var keyPath = flag.String("keyPath", "certs/key.pem", "Path for server private key")
+
flag.Parse()
if loglevel, err := log.ParseLevel(*logLevelStr); err == nil {
}
go startWebServer(getEcho(), *port)
+ go startHttpsWebServer(getEcho(), *secPort, *certPath, *keyPath)
log.Info("Server started and listening on port: ", *port)
e.Logger.Fatal(e.Start(fmt.Sprintf("0.0.0.0:%d", port)))
}
+func startHttpsWebServer(e *echo.Echo, port int, certPath string, keyPath string) {
+ e.Logger.Fatal(e.StartTLS(fmt.Sprintf("0.0.0.0:%d", port), certPath, keyPath))
+}
+
func keepServerAlive() {
forever := make(chan int)
<-forever
}
func hello(c echo.Context) error {
- return c.String(http.StatusOK, "Hello, World!\n")
+ return c.String(http.StatusOK, "Hello, World!")
}
func getSwagger(c echo.Context) error {
package main
import (
+ "crypto/tls"
+ "fmt"
+ "io"
"net/http"
"testing"
+ "time"
"github.com/deepmap/oapi-codegen/pkg/testutil"
"github.com/getkin/kin-openapi/openapi3"
assert.Contains(t, *errorResponse.Cause, "Invalid API")
assert.Contains(t, *errorResponse.Cause, invalidApi)
}
+
+func TestHTTPSServer(t *testing.T) {
+ e = getEcho()
+ var port = 44333
+ go startHttpsWebServer(e, 44333, "certs/cert.pem", "certs/key.pem") //"certs/test/cert.pem", "certs/test/key.pem"
+
+ time.Sleep(100 * time.Millisecond)
+
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ }
+
+ client := &http.Client{Transport: tr}
+ res, err := client.Get(fmt.Sprintf("https://localhost:%d", port))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ defer res.Body.Close()
+ assert.Equal(t, res.StatusCode, res.StatusCode)
+
+ body, err := io.ReadAll(res.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expected := []byte("Hello, World!")
+ assert.Equal(t, expected, body)
+}