X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=blobdiff_plain;f=capifcore%2Finternal%2Fpublishservice%2Fpublishservice.go;h=52434e8528e79ed104ac3da1c7d46bb65175fc37;hb=3928f2d539956636d31902d9e3650a3a50410df3;hp=e073e38de15e38c0b8635c3bf980ad69b16e6239;hpb=2ba4580c67fbe7994141e4cd2701f7bd22b69ebf;p=nonrtric%2Fplt%2Fsme.git diff --git a/capifcore/internal/publishservice/publishservice.go b/capifcore/internal/publishservice/publishservice.go index e073e38..52434e8 100644 --- a/capifcore/internal/publishservice/publishservice.go +++ b/capifcore/internal/publishservice/publishservice.go @@ -21,6 +21,7 @@ package publishservice import ( + "fmt" "net/http" "path" "strings" @@ -30,7 +31,8 @@ import ( "k8s.io/utils/strings/slices" "oransc.org/nonrtric/capifcore/internal/common29122" - "oransc.org/nonrtric/capifcore/internal/publishserviceapi" + "oransc.org/nonrtric/capifcore/internal/eventsapi" + publishapi "oransc.org/nonrtric/capifcore/internal/publishserviceapi" "oransc.org/nonrtric/capifcore/internal/helmmanagement" "oransc.org/nonrtric/capifcore/internal/providermanagement" @@ -40,26 +42,36 @@ import ( //go:generate mockery --name PublishRegister type PublishRegister interface { - AreAPIsPublished(serviceDescriptions *[]publishserviceapi.ServiceAPIDescription) bool + // 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 } type PublishService struct { - publishedServices map[string][]*publishserviceapi.ServiceAPIDescription + publishedServices map[string][]publishapi.ServiceAPIDescription serviceRegister providermanagement.ServiceRegister helmManager helmmanagement.HelmManager + eventChannel chan<- eventsapi.EventNotification lock sync.Mutex } -func NewPublishService(serviceRegister providermanagement.ServiceRegister, hm helmmanagement.HelmManager) *PublishService { +// Creates a service that implements both the PublishRegister and the publishserviceapi.ServerInterface interfaces. +func NewPublishService(serviceRegister providermanagement.ServiceRegister, hm helmmanagement.HelmManager, eventChannel chan<- eventsapi.EventNotification) *PublishService { return &PublishService{ helmManager: hm, - publishedServices: make(map[string][]*publishserviceapi.ServiceAPIDescription), + publishedServices: make(map[string][]publishapi.ServiceAPIDescription), serviceRegister: serviceRegister, + eventChannel: eventChannel, } } -func (ps *PublishService) AreAPIsPublished(serviceDescriptions *[]publishserviceapi.ServiceAPIDescription) bool { +func (ps *PublishService) AreAPIsPublished(serviceDescriptions *[]publishapi.ServiceAPIDescription) bool { if serviceDescriptions != nil { registeredApis := ps.getAllAefIds() @@ -75,13 +87,13 @@ func (ps *PublishService) getAllAefIds() []string { allIds := []string{} for _, descriptions := range ps.publishedServices { for _, description := range descriptions { - allIds = append(allIds, getIdsFromDescription(*description)...) + allIds = append(allIds, getIdsFromDescription(description)...) } } return allIds } -func getIdsFromDescription(description publishserviceapi.ServiceAPIDescription) []string { +func getIdsFromDescription(description publishapi.ServiceAPIDescription) []string { allIds := []string{} if description.AefProfiles != nil { for _, aefProfile := range *description.AefProfiles { @@ -91,7 +103,7 @@ func getIdsFromDescription(description publishserviceapi.ServiceAPIDescription) return allIds } -func checkNewDescriptions(newDescriptions []publishserviceapi.ServiceAPIDescription, registeredAefIds []string) bool { +func checkNewDescriptions(newDescriptions []publishapi.ServiceAPIDescription, registeredAefIds []string) bool { registered := true for _, newApi := range newDescriptions { if !checkProfiles(newApi.AefProfiles, registeredAefIds) { @@ -102,7 +114,7 @@ func checkNewDescriptions(newDescriptions []publishserviceapi.ServiceAPIDescript return registered } -func checkProfiles(newProfiles *[]publishserviceapi.AefProfile, registeredAefIds []string) bool { +func checkProfiles(newProfiles *[]publishapi.AefProfile, registeredAefIds []string) bool { allRegistered := true if newProfiles != nil { for _, profile := range *newProfiles { @@ -119,15 +131,36 @@ func (ps *PublishService) IsAPIPublished(aefId, path string) bool { return slices.Contains(ps.getAllAefIds(), aefId) } +func (ps *PublishService) GetAllPublishedServices() []publishapi.ServiceAPIDescription { + publishedDescriptions := []publishapi.ServiceAPIDescription{} + for _, descriptions := range ps.publishedServices { + publishedDescriptions = append(publishedDescriptions, descriptions...) + } + return publishedDescriptions +} + +// Retrieve all published APIs. func (ps *PublishService) GetApfIdServiceApis(ctx echo.Context, apfId string) error { - return ctx.NoContent(http.StatusNotImplemented) + serviceDescriptions, ok := ps.publishedServices[apfId] + if ok { + err := ctx.JSON(http.StatusOK, serviceDescriptions) + if err != nil { + // Something really bad happened, tell Echo that our handler failed + return err + } + } else { + return sendCoreError(ctx, http.StatusNotFound, fmt.Sprintf("Provider %s not registered", apfId)) + } + + return nil } +// Publish a new API. func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) error { - var newServiceAPIDescription publishserviceapi.ServiceAPIDescription + var newServiceAPIDescription publishapi.ServiceAPIDescription err := ctx.Bind(&newServiceAPIDescription) if err != nil { - return sendCoreError(ctx, http.StatusBadRequest, "Invalid format for service") + return sendCoreError(ctx, http.StatusBadRequest, "Invalid format for service "+apfId) } ps.lock.Lock() @@ -136,25 +169,24 @@ func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) e registeredFuncs := ps.serviceRegister.GetAefsForPublisher(apfId) for _, profile := range *newServiceAPIDescription.AefProfiles { if !slices.Contains(registeredFuncs, profile.AefId) { - return sendCoreError(ctx, http.StatusNotFound, "Function not registered, "+profile.AefId) + return sendCoreError(ctx, http.StatusNotFound, fmt.Sprintf("Function %s not registered", profile.AefId)) } } newId := "api_id_" + newServiceAPIDescription.ApiName newServiceAPIDescription.ApiId = &newId - info := strings.Split(*newServiceAPIDescription.Description, ",") - if len(info) == 5 { - err = ps.helmManager.InstallHelmChart(info[1], info[2], info[3], info[4]) - if err != nil { - return sendCoreError(ctx, http.StatusBadRequest, "Unable to install Helm chart due to: "+err.Error()) - } - log.Info("Installed service: ", newId) + + shouldReturn, returnValue := ps.installHelmChart(newServiceAPIDescription, ctx) + if shouldReturn { + return returnValue } + go ps.sendEvent(newServiceAPIDescription, eventsapi.CAPIFEventSERVICEAPIAVAILABLE) + _, ok := ps.publishedServices[apfId] if ok { - ps.publishedServices[apfId] = append(ps.publishedServices[apfId], &newServiceAPIDescription) + ps.publishedServices[apfId] = append(ps.publishedServices[apfId], newServiceAPIDescription) } else { - ps.publishedServices[apfId] = append([]*publishserviceapi.ServiceAPIDescription{}, &newServiceAPIDescription) + ps.publishedServices[apfId] = append([]publishapi.ServiceAPIDescription{}, newServiceAPIDescription) } uri := ctx.Request().Host + ctx.Request().URL.String() @@ -168,6 +200,19 @@ func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) e return nil } +func (ps *PublishService) installHelmChart(newServiceAPIDescription publishapi.ServiceAPIDescription, ctx echo.Context) (bool, error) { + info := strings.Split(*newServiceAPIDescription.Description, ",") + if len(info) == 5 { + 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())) + } + log.Debug("Installed service: ", newServiceAPIDescription.ApiId) + } + return false, nil +} + +// Unpublish a published service API. func (ps *PublishService) DeleteApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error { serviceDescriptions, ok := ps.publishedServices[string(apfId)] if ok { @@ -176,21 +221,23 @@ func (ps *PublishService) DeleteApfIdServiceApisServiceApiId(ctx echo.Context, a info := strings.Split(*description.Description, ",") if len(info) == 5 { ps.helmManager.UninstallHelmChart(info[1], info[3]) - log.Info("Deleted service: ", serviceApiId) + 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) } } return ctx.NoContent(http.StatusNoContent) } +// 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 { @@ -207,28 +254,157 @@ func (ps *PublishService) GetApfIdServiceApisServiceApiId(ctx echo.Context, apfI return ctx.NoContent(http.StatusNotFound) } -func getServiceDescription(serviceApiId string, descriptions []*publishserviceapi.ServiceAPIDescription) (int, *publishserviceapi.ServiceAPIDescription) { +func getServiceDescription(serviceApiId string, descriptions []publishapi.ServiceAPIDescription) (int, *publishapi.ServiceAPIDescription) { for pos, description := range descriptions { if serviceApiId == *description.ApiId { - return pos, description + return pos, &description } } return -1, nil } -func removeServiceDescription(i int, a []*publishserviceapi.ServiceAPIDescription) []*publishserviceapi.ServiceAPIDescription { - a[i] = a[len(a)-1] // Copy last element to index i. - a[len(a)-1] = nil // Erase last element (write zero value). - a = a[:len(a)-1] // Truncate slice. +func removeServiceDescription(i int, a []publishapi.ServiceAPIDescription) []publishapi.ServiceAPIDescription { + a[i] = a[len(a)-1] // Copy last element to index i. + a[len(a)-1] = publishapi.ServiceAPIDescription{} // Erase last element (write zero value). + a = a[:len(a)-1] // Truncate slice. return a } +// Modify an existing published service API. func (ps *PublishService) ModifyIndAPFPubAPI(ctx echo.Context, apfId string, serviceApiId string) error { return ctx.NoContent(http.StatusNotImplemented) } +// Update a published service API. func (ps *PublishService) PutApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error { - return ctx.NoContent(http.StatusNotImplemented) + pos, publishedService, shouldReturn, returnValue := ps.checkIfServiceIsPublished(apfId, serviceApiId, ctx) + if shouldReturn { + return returnValue + } + + updatedServiceDescription, shouldReturn, returnValue := getServiceFromRequest(ctx) + if shouldReturn { + return returnValue + } + + 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) + } + + pos, shouldReturn, returnValue = ps.updateProfiles(pos, apfId, updatedServiceDescription, publishedService, ctx) + if shouldReturn { + return returnValue + } + + err := ctx.JSON(http.StatusOK, ps.publishedServices[apfId][pos]) + 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) { + publishedServices, ok := ps.publishedServices[apfId] + if !ok { + return 0, publishapi.ServiceAPIDescription{}, true, sendCoreError(ctx, http.StatusBadRequest, "Service must be published before updating it") + } else { + for pos, description := range publishedServices { + if *description.ApiId == serviceApiId { + return pos, description, false, nil + + } + + } + + } + return 0, publishapi.ServiceAPIDescription{}, true, sendCoreError(ctx, http.StatusBadRequest, "Service must be published before updating it") +} + +func getServiceFromRequest(ctx echo.Context) (publishapi.ServiceAPIDescription, bool, 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 updatedServiceDescription, false, 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 + } + } + + } + return 0, false, nil +} + +func (ps *PublishService) sendEvent(service publishapi.ServiceAPIDescription, eventType eventsapi.CAPIFEvent) { + apiIds := []string{*service.ApiId} + apis := []publishapi.ServiceAPIDescription{service} + event := eventsapi.EventNotification{ + EventDetail: &eventsapi.CAPIFEventDetail{ + ApiIds: &apiIds, + ServiceAPIDescriptions: &apis, + }, + Events: eventType, + } + 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 + } + } + return 0, publishapi.AefProfile{}, fmt.Errorf("profile with ID %s is not registered for the service", profileId) } // This function wraps sending of an error in the Error format, and