2 // ========================LICENSE_START=================================
5 // Copyright (C) 2022: Nordix Foundation
7 // Licensed under the Apache License, Version 2.0 (the "License");
8 // you may not use this file except in compliance with the License.
9 // You may obtain a copy of the License at
11 // http://www.apache.org/licenses/LICENSE-2.0
13 // Unless required by applicable law or agreed to in writing, software
14 // distributed under the License is distributed on an "AS IS" BASIS,
15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 // See the License for the specific language governing permissions and
17 // limitations under the License.
18 // ========================LICENSE_END===================================
21 package publishservice
30 "github.com/labstack/echo/v4"
31 "k8s.io/utils/strings/slices"
33 "oransc.org/nonrtric/capifcore/internal/common29122"
34 publishapi "oransc.org/nonrtric/capifcore/internal/publishserviceapi"
36 "oransc.org/nonrtric/capifcore/internal/helmmanagement"
37 "oransc.org/nonrtric/capifcore/internal/providermanagement"
39 log "github.com/sirupsen/logrus"
42 //go:generate mockery --name PublishRegister
43 type PublishRegister interface {
44 // Checks if the provided APIs are published.
45 // Returns true if all provided APIs have been published, false otherwise.
46 AreAPIsPublished(serviceDescriptions *[]publishapi.ServiceAPIDescription) bool
47 // Checks if the provided API is published.
48 // Returns true if the provided API has been published, false otherwise.
49 IsAPIPublished(aefId, path string) bool
50 // Gets all published APIs.
51 // Returns a list of all APIs that has been published.
52 GetAllPublishedServices() []publishapi.ServiceAPIDescription
55 type PublishService struct {
56 publishedServices map[string][]publishapi.ServiceAPIDescription
57 serviceRegister providermanagement.ServiceRegister
58 helmManager helmmanagement.HelmManager
62 // Creates a service that implements both the PublishRegister and the publishserviceapi.ServerInterface interfaces.
63 func NewPublishService(serviceRegister providermanagement.ServiceRegister, hm helmmanagement.HelmManager) *PublishService {
64 return &PublishService{
66 publishedServices: make(map[string][]publishapi.ServiceAPIDescription),
67 serviceRegister: serviceRegister,
71 func (ps *PublishService) AreAPIsPublished(serviceDescriptions *[]publishapi.ServiceAPIDescription) bool {
73 if serviceDescriptions != nil {
74 registeredApis := ps.getAllAefIds()
75 return checkNewDescriptions(*serviceDescriptions, registeredApis)
80 func (ps *PublishService) getAllAefIds() []string {
82 defer ps.lock.Unlock()
85 for _, descriptions := range ps.publishedServices {
86 for _, description := range descriptions {
87 allIds = append(allIds, getIdsFromDescription(description)...)
93 func getIdsFromDescription(description publishapi.ServiceAPIDescription) []string {
95 if description.AefProfiles != nil {
96 for _, aefProfile := range *description.AefProfiles {
97 allIds = append(allIds, aefProfile.AefId)
103 func checkNewDescriptions(newDescriptions []publishapi.ServiceAPIDescription, registeredAefIds []string) bool {
105 for _, newApi := range newDescriptions {
106 if !checkProfiles(newApi.AefProfiles, registeredAefIds) {
114 func checkProfiles(newProfiles *[]publishapi.AefProfile, registeredAefIds []string) bool {
115 allRegistered := true
116 if newProfiles != nil {
117 for _, profile := range *newProfiles {
118 if !slices.Contains(registeredAefIds, profile.AefId) {
119 allRegistered = false
127 func (ps *PublishService) IsAPIPublished(aefId, path string) bool {
128 return slices.Contains(ps.getAllAefIds(), aefId)
131 func (ps *PublishService) GetAllPublishedServices() []publishapi.ServiceAPIDescription {
132 publishedDescriptions := []publishapi.ServiceAPIDescription{}
133 for _, descriptions := range ps.publishedServices {
134 publishedDescriptions = append(publishedDescriptions, descriptions...)
136 return publishedDescriptions
139 // Retrieve all published APIs.
140 func (ps *PublishService) GetApfIdServiceApis(ctx echo.Context, apfId string) error {
141 serviceDescriptions, ok := ps.publishedServices[apfId]
143 err := ctx.JSON(http.StatusOK, serviceDescriptions)
145 // Something really bad happened, tell Echo that our handler failed
149 return sendCoreError(ctx, http.StatusNotFound, fmt.Sprintf("Provider %s not registered", apfId))
155 // Publish a new API.
156 func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) error {
157 var newServiceAPIDescription publishapi.ServiceAPIDescription
158 err := ctx.Bind(&newServiceAPIDescription)
160 return sendCoreError(ctx, http.StatusBadRequest, "Invalid format for service "+apfId)
164 defer ps.lock.Unlock()
166 registeredFuncs := ps.serviceRegister.GetAefsForPublisher(apfId)
167 for _, profile := range *newServiceAPIDescription.AefProfiles {
168 if !slices.Contains(registeredFuncs, profile.AefId) {
169 return sendCoreError(ctx, http.StatusNotFound, fmt.Sprintf("Function %s not registered", profile.AefId))
173 newId := "api_id_" + newServiceAPIDescription.ApiName
174 newServiceAPIDescription.ApiId = &newId
176 shouldReturn, returnValue := ps.installHelmChart(newServiceAPIDescription, ctx)
181 _, ok := ps.publishedServices[apfId]
183 ps.publishedServices[apfId] = append(ps.publishedServices[apfId], newServiceAPIDescription)
185 ps.publishedServices[apfId] = append([]publishapi.ServiceAPIDescription{}, newServiceAPIDescription)
188 uri := ctx.Request().Host + ctx.Request().URL.String()
189 ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, *newServiceAPIDescription.ApiId))
190 err = ctx.JSON(http.StatusCreated, newServiceAPIDescription)
192 // Something really bad happened, tell Echo that our handler failed
199 func (ps *PublishService) installHelmChart(newServiceAPIDescription publishapi.ServiceAPIDescription, ctx echo.Context) (bool, error) {
200 info := strings.Split(*newServiceAPIDescription.Description, ",")
202 err := ps.helmManager.InstallHelmChart(info[1], info[2], info[3], info[4])
204 return true, sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf("Unable to install Helm chart %s due to: %s", info[3], err.Error()))
206 log.Debug("Installed service: ", newServiceAPIDescription.ApiId)
211 // Unpublish a published service API.
212 func (ps *PublishService) DeleteApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
213 serviceDescriptions, ok := ps.publishedServices[string(apfId)]
215 pos, description := getServiceDescription(serviceApiId, serviceDescriptions)
216 if description != nil {
217 info := strings.Split(*description.Description, ",")
219 ps.helmManager.UninstallHelmChart(info[1], info[3])
220 log.Debug("Deleted service: ", serviceApiId)
223 defer ps.lock.Unlock()
224 ps.publishedServices[string(apfId)] = removeServiceDescription(pos, serviceDescriptions)
227 return ctx.NoContent(http.StatusNoContent)
230 // Retrieve a published service API.
231 func (ps *PublishService) GetApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
233 defer ps.lock.Unlock()
235 serviceDescriptions, ok := ps.publishedServices[apfId]
237 _, serviceDescription := getServiceDescription(serviceApiId, serviceDescriptions)
238 if serviceDescription == nil {
239 return ctx.NoContent(http.StatusNotFound)
241 err := ctx.JSON(http.StatusOK, serviceDescription)
243 // Something really bad happened, tell Echo that our handler failed
249 return ctx.NoContent(http.StatusNotFound)
252 func getServiceDescription(serviceApiId string, descriptions []publishapi.ServiceAPIDescription) (int, *publishapi.ServiceAPIDescription) {
253 for pos, description := range descriptions {
254 if serviceApiId == *description.ApiId {
255 return pos, &description
261 func removeServiceDescription(i int, a []publishapi.ServiceAPIDescription) []publishapi.ServiceAPIDescription {
262 a[i] = a[len(a)-1] // Copy last element to index i.
263 a[len(a)-1] = publishapi.ServiceAPIDescription{} // Erase last element (write zero value).
264 a = a[:len(a)-1] // Truncate slice.
268 // Modify an existing published service API.
269 func (ps *PublishService) ModifyIndAPFPubAPI(ctx echo.Context, apfId string, serviceApiId string) error {
270 return ctx.NoContent(http.StatusNotImplemented)
273 // Update a published service API.
274 func (ps *PublishService) PutApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
275 return ctx.NoContent(http.StatusNotImplemented)
278 // This function wraps sending of an error in the Error format, and
279 // handling the failure to marshal that.
280 func sendCoreError(ctx echo.Context, code int, message string) error {
281 pd := common29122.ProblemDetails{
285 err := ctx.JSON(code, pd)