X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=blobdiff_plain;f=capifcore%2Finternal%2Fpublishservice%2Fpublishservice.go;h=a267acfd242cabfd861bf33ac5aa31e043cd4da5;hb=HEAD;hp=3209024311baa2b8ee42138956b3a36ce72b93e9;hpb=fe5863c7537e25426382bd1e376882c5c7d81a4a;p=nonrtric%2Fplt%2Fsme.git diff --git a/capifcore/internal/publishservice/publishservice.go b/capifcore/internal/publishservice/publishservice.go index 3209024..a267acf 100644 --- a/capifcore/internal/publishservice/publishservice.go +++ b/capifcore/internal/publishservice/publishservice.go @@ -2,7 +2,8 @@ // ========================LICENSE_START================================= // O-RAN-SC // %% -// Copyright (C) 2022: Nordix Foundation +// Copyright (C) 2022-2023: Nordix Foundation +// Copyright (C) 2024: OpenInfra Foundation Europe // %% // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,7 +28,7 @@ import ( "strings" "sync" - "github.com/labstack/echo/v4" + echo "github.com/labstack/echo/v4" "k8s.io/utils/strings/slices" "oransc.org/nonrtric/capifcore/internal/common29122" @@ -42,15 +43,13 @@ import ( //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 // Gets all published APIs. // Returns a list of all APIs that has been published. GetAllPublishedServices() []publishapi.ServiceAPIDescription + GetAllowedPublishedServices(invokerApiList []publishapi.ServiceAPIDescription) []publishapi.ServiceAPIDescription } type PublishService struct { @@ -71,15 +70,6 @@ func NewPublishService(serviceRegister providermanagement.ServiceRegister, hm he } } -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() @@ -87,46 +77,12 @@ func (ps *PublishService) getAllAefIds() []string { 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) } @@ -139,9 +95,36 @@ func (ps *PublishService) GetAllPublishedServices() []publishapi.ServiceAPIDescr return publishedDescriptions } +func (ps *PublishService) GetAllowedPublishedServices(apiListRequestedServices []publishapi.ServiceAPIDescription) []publishapi.ServiceAPIDescription { + apiListAllPublished := ps.GetAllPublishedServices() + allowedPublishedServices := join(apiListAllPublished, apiListRequestedServices) + return allowedPublishedServices +} + +func join(a, b []publishapi.ServiceAPIDescription) []publishapi.ServiceAPIDescription { + var result []publishapi.ServiceAPIDescription + + if (a == nil) || (b == nil) || (len(a) == 0) || (len(b) == 0) { + return result + } + + for _, itemA := range a { + for _, itemB := range b { + if itemA.ApiName == itemB.ApiName { + result = append(result, itemA) + break + } + } + } + return result +} + // Retrieve all published APIs. func (ps *PublishService) GetApfIdServiceApis(ctx echo.Context, apfId string) error { + ps.lock.Lock() serviceDescriptions, ok := ps.publishedServices[apfId] + ps.lock.Unlock() + if ok { err := ctx.JSON(http.StatusOK, serviceDescriptions) if err != nil { @@ -149,32 +132,52 @@ func (ps *PublishService) GetApfIdServiceApis(ctx echo.Context, apfId string) er return err } } else { - return sendCoreError(ctx, http.StatusNotFound, fmt.Sprintf("Provider %s not registered", apfId)) - } + if !ps.serviceRegister.IsPublishingFunctionRegistered(apfId) { + errorMsg := fmt.Sprintf("Unable to get the service due to %s api is only available for publishers", apfId) + return sendCoreError(ctx, http.StatusNotFound, errorMsg) + } + serviceDescriptions = []publishapi.ServiceAPIDescription{} + err := ctx.JSON(http.StatusOK, serviceDescriptions) + if err != nil { + // Something really bad happened, tell Echo that our handler failed + return err + } + } return nil } // 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 !ps.serviceRegister.IsPublishingFunctionRegistered(apfId) { + return sendCoreError(ctx, http.StatusForbidden, fmt.Sprintf(errorMsg, "api is only available for publishers "+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 { @@ -200,9 +203,20 @@ func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) e 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 { + if (len(info) == 5) && (ps.helmManager != nil) { err := ps.helmManager.InstallHelmChart(info[1], info[2], info[3], info[4]) if err != nil { return true, sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf("Unable to install Helm chart %s due to: %s", info[3], err.Error())) @@ -219,7 +233,7 @@ func (ps *PublishService) DeleteApfIdServiceApisServiceApiId(ctx echo.Context, a pos, description := getServiceDescription(serviceApiId, serviceDescriptions) if description != nil { info := strings.Split(*description.Description, ",") - if len(info) == 5 { + if (len(info) == 5) && (ps.helmManager != nil) { ps.helmManager.UninstallHelmChart(info[1], info[3]) log.Debug("Deleted service: ", serviceApiId) } @@ -256,7 +270,8 @@ func (ps *PublishService) GetApfIdServiceApisServiceApiId(ctx echo.Context, apfI func getServiceDescription(serviceApiId string, descriptions []publishapi.ServiceAPIDescription) (int, *publishapi.ServiceAPIDescription) { for pos, description := range descriptions { - if serviceApiId == *description.ApiId { + // Check for nil as we had a failure here when running unit tests in parallel against a single Capifcore instance + if (description.ApiId != nil) && (serviceApiId == *description.ApiId) { return pos, &description } } @@ -280,20 +295,33 @@ func (ps *PublishService) PutApfIdServiceApisServiceApiId(ctx echo.Context, apfI ps.lock.Lock() defer ps.lock.Unlock() errMsg := "Unable to update service due to %s." - pos, publishedService, err := ps.checkIfServiceIsPublished(apfId, serviceApiId, ctx) + + pos, publishedService, err := ps.checkIfServiceIsPublished(apfId, serviceApiId) if err != nil { return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err)) } + updatedServiceDescription, err := getServiceFromRequest(ctx) if err != nil { return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err)) } - ps.updateDescription(pos, apfId, &updatedServiceDescription, &publishedService) - pos, publishedService.AefProfiles, err = ps.updateProfiles(pos, apfId, updatedServiceDescription.AefProfiles, publishedService.AefProfiles) + + // Additional validation for PUT + if (updatedServiceDescription.ApiId == nil) || (*updatedServiceDescription.ApiId != serviceApiId) { + errDetail := "ServiceAPIDescription ApiId doesn't match path parameter" + return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, errDetail)) + } + + err = ps.checkProfilesRegistered(apfId, *updatedServiceDescription.AefProfiles) if err != nil { return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err)) } + + ps.updateDescription(&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 @@ -301,7 +329,8 @@ func (ps *PublishService) PutApfIdServiceApisServiceApiId(ctx echo.Context, apfI } return nil } -func (ps *PublishService) checkIfServiceIsPublished(apfId string, serviceApiId string, ctx echo.Context) (int, publishapi.ServiceAPIDescription, error) { + +func (ps *PublishService) checkIfServiceIsPublished(apfId string, serviceApiId string) (int, publishapi.ServiceAPIDescription, error) { publishedServices, ok := ps.publishedServices[apfId] if !ok { return 0, publishapi.ServiceAPIDescription{}, fmt.Errorf("service must be published before updating it") @@ -314,6 +343,7 @@ func (ps *PublishService) checkIfServiceIsPublished(apfId string, serviceApiId s } return 0, publishapi.ServiceAPIDescription{}, fmt.Errorf("service must be published before updating it") } + func getServiceFromRequest(ctx echo.Context) (publishapi.ServiceAPIDescription, error) { var updatedServiceDescription publishapi.ServiceAPIDescription err := ctx.Bind(&updatedServiceDescription) @@ -322,7 +352,8 @@ func getServiceFromRequest(ctx echo.Context) (publishapi.ServiceAPIDescription, } return updatedServiceDescription, nil } -func (ps *PublishService) updateDescription(pos int, apfId string, updatedServiceDescription, publishedService *publishapi.ServiceAPIDescription) { + +func (ps *PublishService) updateDescription(updatedServiceDescription, publishedService *publishapi.ServiceAPIDescription) { if updatedServiceDescription.Description != nil { publishedService.Description = updatedServiceDescription.Description go ps.sendEvent(*publishedService, eventsapi.CAPIFEventSERVICEAPIUPDATE) @@ -342,43 +373,14 @@ func (ps *PublishService) sendEvent(service publishapi.ServiceAPIDescription, ev ps.eventChannel <- event } -func (ps *PublishService) updateProfiles(pos int, apfId string, updatedServiceDescription *[]publishapi.AefProfile, publishedService *[]publishapi.AefProfile) (int, *[]publishapi.AefProfile, error) { +func (ps *PublishService) checkProfilesRegistered(apfId string, updatedProfiles []publishapi.AefProfile) error { registeredFuncs := ps.serviceRegister.GetAefsForPublisher(apfId) - addedProfiles := []publishapi.AefProfile{} - changedProfiles := []publishapi.AefProfile{} - for _, profile := range *updatedServiceDescription { + for _, profile := range updatedProfiles { if !slices.Contains(registeredFuncs, profile.AefId) { - return 0, nil, fmt.Errorf("function %s not registered", profile.AefId) - } - for _, publishedProfile := range *publishedService { - if publishedProfile.AefId != profile.AefId { - registeredProfiles := *publishedService - newProfiles := append(registeredProfiles, profile) - publishedService = &newProfiles - addedProfiles = append(addedProfiles, profile) - } else { - pos, registeredProfile, err := getProfile(profile.AefId, publishedService) - if err != nil { - return pos, updatedServiceDescription, fmt.Errorf("unable to update service due to: %s", err.Error()) - } - if profile.DomainName != nil { - registeredProfile.DomainName = profile.DomainName - (*publishedService)[pos] = registeredProfile - } - changedProfiles = append(changedProfiles, profile) - } + return fmt.Errorf("function %s not registered", profile.AefId) } } - modifiedProfiles := append(changedProfiles, addedProfiles...) - return 0, &modifiedProfiles, 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 - } - } - 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