Updates for G Maintenance release
[nonrtric/plt/sme.git] / capifcore / internal / publishservice / publishservice.go
1 // -
2 //   ========================LICENSE_START=================================
3 //   O-RAN-SC
4 //   %%
5 //   Copyright (C) 2022: Nordix Foundation
6 //   %%
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
10 //
11 //        http://www.apache.org/licenses/LICENSE-2.0
12 //
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===================================
19 //
20
21 package publishservice
22
23 import (
24         "fmt"
25         "net/http"
26         "path"
27         "strings"
28         "sync"
29
30         "github.com/labstack/echo/v4"
31         "k8s.io/utils/strings/slices"
32
33         "oransc.org/nonrtric/capifcore/internal/common29122"
34         "oransc.org/nonrtric/capifcore/internal/eventsapi"
35         publishapi "oransc.org/nonrtric/capifcore/internal/publishserviceapi"
36
37         "oransc.org/nonrtric/capifcore/internal/helmmanagement"
38         "oransc.org/nonrtric/capifcore/internal/providermanagement"
39
40         log "github.com/sirupsen/logrus"
41 )
42
43 //go:generate mockery --name PublishRegister
44 type PublishRegister interface {
45         // Checks if the provided API is published.
46         // Returns true if the provided API has been published, false otherwise.
47         IsAPIPublished(aefId, path string) bool
48         // Gets all published APIs.
49         // Returns a list of all APIs that has been published.
50         GetAllPublishedServices() []publishapi.ServiceAPIDescription
51 }
52
53 type PublishService struct {
54         publishedServices map[string][]publishapi.ServiceAPIDescription
55         serviceRegister   providermanagement.ServiceRegister
56         helmManager       helmmanagement.HelmManager
57         eventChannel      chan<- eventsapi.EventNotification
58         lock              sync.Mutex
59 }
60
61 // Creates a service that implements both the PublishRegister and the publishserviceapi.ServerInterface interfaces.
62 func NewPublishService(serviceRegister providermanagement.ServiceRegister, hm helmmanagement.HelmManager, eventChannel chan<- eventsapi.EventNotification) *PublishService {
63         return &PublishService{
64                 helmManager:       hm,
65                 publishedServices: make(map[string][]publishapi.ServiceAPIDescription),
66                 serviceRegister:   serviceRegister,
67                 eventChannel:      eventChannel,
68         }
69 }
70
71 func (ps *PublishService) getAllAefIds() []string {
72         ps.lock.Lock()
73         defer ps.lock.Unlock()
74
75         allIds := []string{}
76         for _, descriptions := range ps.publishedServices {
77                 for _, description := range descriptions {
78                         allIds = append(allIds, description.GetAefIds()...)
79                 }
80         }
81         return allIds
82 }
83
84 func (ps *PublishService) IsAPIPublished(aefId, path string) bool {
85         return slices.Contains(ps.getAllAefIds(), aefId)
86 }
87
88 func (ps *PublishService) GetAllPublishedServices() []publishapi.ServiceAPIDescription {
89         publishedDescriptions := []publishapi.ServiceAPIDescription{}
90         for _, descriptions := range ps.publishedServices {
91                 publishedDescriptions = append(publishedDescriptions, descriptions...)
92         }
93         return publishedDescriptions
94 }
95
96 // Retrieve all published APIs.
97 func (ps *PublishService) GetApfIdServiceApis(ctx echo.Context, apfId string) error {
98         serviceDescriptions, ok := ps.publishedServices[apfId]
99         if ok {
100                 err := ctx.JSON(http.StatusOK, serviceDescriptions)
101                 if err != nil {
102                         // Something really bad happened, tell Echo that our handler failed
103                         return err
104                 }
105         } else {
106                 return sendCoreError(ctx, http.StatusNotFound, fmt.Sprintf("Provider %s not registered", apfId))
107         }
108
109         return nil
110 }
111
112 // Publish a new API.
113 func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) error {
114         var newServiceAPIDescription publishapi.ServiceAPIDescription
115         errorMsg := "Unable to publish the service due to %s "
116         err := ctx.Bind(&newServiceAPIDescription)
117         if err != nil {
118                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errorMsg, "invalid format for service "+apfId))
119         }
120
121         if err := ps.isServicePublished(newServiceAPIDescription); err != nil {
122                 return sendCoreError(ctx, http.StatusForbidden, fmt.Sprintf(errorMsg, err))
123         }
124
125         if err := newServiceAPIDescription.Validate(); err != nil {
126                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errorMsg, err))
127         }
128         ps.lock.Lock()
129         defer ps.lock.Unlock()
130
131         registeredFuncs := ps.serviceRegister.GetAefsForPublisher(apfId)
132         for _, profile := range *newServiceAPIDescription.AefProfiles {
133                 if !slices.Contains(registeredFuncs, profile.AefId) {
134                         return sendCoreError(ctx, http.StatusNotFound, fmt.Sprintf(errorMsg, fmt.Sprintf("function %s not registered", profile.AefId)))
135                 }
136         }
137
138         newServiceAPIDescription.PrepareNewService()
139
140         shouldReturn, returnValue := ps.installHelmChart(newServiceAPIDescription, ctx)
141         if shouldReturn {
142                 return returnValue
143         }
144         go ps.sendEvent(newServiceAPIDescription, eventsapi.CAPIFEventSERVICEAPIAVAILABLE)
145
146         _, ok := ps.publishedServices[apfId]
147         if ok {
148                 ps.publishedServices[apfId] = append(ps.publishedServices[apfId], newServiceAPIDescription)
149         } else {
150                 ps.publishedServices[apfId] = append([]publishapi.ServiceAPIDescription{}, newServiceAPIDescription)
151         }
152
153         uri := ctx.Request().Host + ctx.Request().URL.String()
154         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, *newServiceAPIDescription.ApiId))
155         err = ctx.JSON(http.StatusCreated, newServiceAPIDescription)
156         if err != nil {
157                 // Something really bad happened, tell Echo that our handler failed
158                 return err
159         }
160
161         return nil
162 }
163
164 func (ps *PublishService) isServicePublished(newService publishapi.ServiceAPIDescription) error {
165         for _, services := range ps.publishedServices {
166                 for _, service := range services {
167                         if err := service.ValidateAlreadyPublished(newService); err != nil {
168                                 return err
169                         }
170                 }
171         }
172         return nil
173 }
174
175 func (ps *PublishService) installHelmChart(newServiceAPIDescription publishapi.ServiceAPIDescription, ctx echo.Context) (bool, error) {
176         info := strings.Split(*newServiceAPIDescription.Description, ",")
177         if len(info) == 5 {
178                 err := ps.helmManager.InstallHelmChart(info[1], info[2], info[3], info[4])
179                 if err != nil {
180                         return true, sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf("Unable to install Helm chart %s due to: %s", info[3], err.Error()))
181                 }
182                 log.Debug("Installed service: ", newServiceAPIDescription.ApiId)
183         }
184         return false, nil
185 }
186
187 // Unpublish a published service API.
188 func (ps *PublishService) DeleteApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
189         serviceDescriptions, ok := ps.publishedServices[string(apfId)]
190         if ok {
191                 pos, description := getServiceDescription(serviceApiId, serviceDescriptions)
192                 if description != nil {
193                         info := strings.Split(*description.Description, ",")
194                         if len(info) == 5 {
195                                 ps.helmManager.UninstallHelmChart(info[1], info[3])
196                                 log.Debug("Deleted service: ", serviceApiId)
197                         }
198                         ps.lock.Lock()
199                         ps.publishedServices[string(apfId)] = removeServiceDescription(pos, serviceDescriptions)
200                         ps.lock.Unlock()
201                         go ps.sendEvent(*description, eventsapi.CAPIFEventSERVICEAPIUNAVAILABLE)
202                 }
203         }
204         return ctx.NoContent(http.StatusNoContent)
205 }
206
207 // Retrieve a published service API.
208 func (ps *PublishService) GetApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
209         ps.lock.Lock()
210         serviceDescriptions, ok := ps.publishedServices[apfId]
211         ps.lock.Unlock()
212
213         if ok {
214                 _, serviceDescription := getServiceDescription(serviceApiId, serviceDescriptions)
215                 if serviceDescription == nil {
216                         return ctx.NoContent(http.StatusNotFound)
217                 }
218                 err := ctx.JSON(http.StatusOK, serviceDescription)
219                 if err != nil {
220                         // Something really bad happened, tell Echo that our handler failed
221                         return err
222                 }
223
224                 return nil
225         }
226         return ctx.NoContent(http.StatusNotFound)
227 }
228
229 func getServiceDescription(serviceApiId string, descriptions []publishapi.ServiceAPIDescription) (int, *publishapi.ServiceAPIDescription) {
230         for pos, description := range descriptions {
231                 if serviceApiId == *description.ApiId {
232                         return pos, &description
233                 }
234         }
235         return -1, nil
236 }
237
238 func removeServiceDescription(i int, a []publishapi.ServiceAPIDescription) []publishapi.ServiceAPIDescription {
239         a[i] = a[len(a)-1]                               // Copy last element to index i.
240         a[len(a)-1] = publishapi.ServiceAPIDescription{} // Erase last element (write zero value).
241         a = a[:len(a)-1]                                 // Truncate slice.
242         return a
243 }
244
245 // Modify an existing published service API.
246 func (ps *PublishService) ModifyIndAPFPubAPI(ctx echo.Context, apfId string, serviceApiId string) error {
247         return ctx.NoContent(http.StatusNotImplemented)
248 }
249
250 // Update a published service API.
251 func (ps *PublishService) PutApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
252         ps.lock.Lock()
253         defer ps.lock.Unlock()
254         errMsg := "Unable to update service due to %s."
255         pos, publishedService, err := ps.checkIfServiceIsPublished(apfId, serviceApiId, ctx)
256         if err != nil {
257                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
258         }
259         updatedServiceDescription, err := getServiceFromRequest(ctx)
260         if err != nil {
261                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
262         }
263         err = ps.checkProfilesRegistered(apfId, *updatedServiceDescription.AefProfiles)
264         if err != nil {
265                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
266         }
267         ps.updateDescription(pos, apfId, &updatedServiceDescription, &publishedService)
268         publishedService.AefProfiles = updatedServiceDescription.AefProfiles
269         ps.publishedServices[apfId][pos] = publishedService
270         err = ctx.JSON(http.StatusOK, publishedService)
271         if err != nil {
272                 // Something really bad happened, tell Echo that our handler failed
273                 return err
274         }
275         return nil
276 }
277 func (ps *PublishService) checkIfServiceIsPublished(apfId string, serviceApiId string, ctx echo.Context) (int, publishapi.ServiceAPIDescription, error) {
278         publishedServices, ok := ps.publishedServices[apfId]
279         if !ok {
280                 return 0, publishapi.ServiceAPIDescription{}, fmt.Errorf("service must be published before updating it")
281         } else {
282                 for pos, description := range publishedServices {
283                         if *description.ApiId == serviceApiId {
284                                 return pos, description, nil
285                         }
286                 }
287         }
288         return 0, publishapi.ServiceAPIDescription{}, fmt.Errorf("service must be published before updating it")
289 }
290 func getServiceFromRequest(ctx echo.Context) (publishapi.ServiceAPIDescription, error) {
291         var updatedServiceDescription publishapi.ServiceAPIDescription
292         err := ctx.Bind(&updatedServiceDescription)
293         if err != nil {
294                 return publishapi.ServiceAPIDescription{}, fmt.Errorf("invalid format for service")
295         }
296         return updatedServiceDescription, nil
297 }
298 func (ps *PublishService) updateDescription(pos int, apfId string, updatedServiceDescription, publishedService *publishapi.ServiceAPIDescription) {
299         if updatedServiceDescription.Description != nil {
300                 publishedService.Description = updatedServiceDescription.Description
301                 go ps.sendEvent(*publishedService, eventsapi.CAPIFEventSERVICEAPIUPDATE)
302         }
303 }
304
305 func (ps *PublishService) sendEvent(service publishapi.ServiceAPIDescription, eventType eventsapi.CAPIFEvent) {
306         apiIds := []string{*service.ApiId}
307         apis := []publishapi.ServiceAPIDescription{service}
308         event := eventsapi.EventNotification{
309                 EventDetail: &eventsapi.CAPIFEventDetail{
310                         ApiIds:                 &apiIds,
311                         ServiceAPIDescriptions: &apis,
312                 },
313                 Events: eventType,
314         }
315         ps.eventChannel <- event
316 }
317
318 func (ps *PublishService) checkProfilesRegistered(apfId string, updatedProfiles []publishapi.AefProfile) error {
319         registeredFuncs := ps.serviceRegister.GetAefsForPublisher(apfId)
320         for _, profile := range updatedProfiles {
321                 if !slices.Contains(registeredFuncs, profile.AefId) {
322                         return fmt.Errorf("function %s not registered", profile.AefId)
323                 }
324         }
325         return nil
326 }
327
328 // This function wraps sending of an error in the Error format, and
329 // handling the failure to marshal that.
330 func sendCoreError(ctx echo.Context, code int, message string) error {
331         pd := common29122.ProblemDetails{
332                 Cause:  &message,
333                 Status: &code,
334         }
335         err := ctx.JSON(code, pd)
336         return err
337 }