c43524b3d5dd18aa799f9e6f10811513ac79d078
[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         "net/http"
25         "path"
26         "strings"
27         "sync"
28
29         "github.com/labstack/echo/v4"
30         "k8s.io/utils/strings/slices"
31
32         "oransc.org/nonrtric/capifcore/internal/common29122"
33         "oransc.org/nonrtric/capifcore/internal/publishserviceapi"
34
35         "oransc.org/nonrtric/capifcore/internal/helmmanagement"
36         "oransc.org/nonrtric/capifcore/internal/providermanagement"
37
38         log "github.com/sirupsen/logrus"
39 )
40
41 //go:generate mockery --name PublishRegister
42 type PublishRegister interface {
43         AreAPIsPublished(serviceDescriptions *[]publishserviceapi.ServiceAPIDescription) bool
44         IsAPIPublished(aefId, path string) bool
45 }
46
47 type PublishService struct {
48         publishedServices map[string][]*publishserviceapi.ServiceAPIDescription
49         serviceRegister   providermanagement.ServiceRegister
50         helmManager       helmmanagement.HelmManager
51         lock              sync.Mutex
52 }
53
54 func NewPublishService(serviceRegister providermanagement.ServiceRegister, hm helmmanagement.HelmManager) *PublishService {
55         return &PublishService{
56                 helmManager:       hm,
57                 publishedServices: make(map[string][]*publishserviceapi.ServiceAPIDescription),
58                 serviceRegister:   serviceRegister,
59         }
60 }
61
62 func (ps *PublishService) AreAPIsPublished(serviceDescriptions *[]publishserviceapi.ServiceAPIDescription) bool {
63
64         if serviceDescriptions != nil {
65                 registeredApis := ps.getAllAefIds()
66                 return checkNewDescriptions(*serviceDescriptions, registeredApis)
67         }
68         return true
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, getIdsFromDescription(*description)...)
79                 }
80         }
81         return allIds
82 }
83
84 func getIdsFromDescription(description publishserviceapi.ServiceAPIDescription) []string {
85         allIds := []string{}
86         if description.AefProfiles != nil {
87                 for _, aefProfile := range *description.AefProfiles {
88                         allIds = append(allIds, aefProfile.AefId)
89                 }
90         }
91         return allIds
92 }
93
94 func checkNewDescriptions(newDescriptions []publishserviceapi.ServiceAPIDescription, registeredAefIds []string) bool {
95         registered := true
96         for _, newApi := range newDescriptions {
97                 if !checkProfiles(newApi.AefProfiles, registeredAefIds) {
98                         registered = false
99                         break
100                 }
101         }
102         return registered
103 }
104
105 func checkProfiles(newProfiles *[]publishserviceapi.AefProfile, registeredAefIds []string) bool {
106         allRegistered := true
107         if newProfiles != nil {
108                 for _, profile := range *newProfiles {
109                         if !slices.Contains(registeredAefIds, profile.AefId) {
110                                 allRegistered = false
111                                 break
112                         }
113                 }
114         }
115         return allRegistered
116 }
117
118 func (ps *PublishService) IsAPIPublished(aefId, path string) bool {
119         return slices.Contains(ps.getAllAefIds(), aefId)
120 }
121
122 func (ps *PublishService) GetApfIdServiceApis(ctx echo.Context, apfId string) error {
123         serviceDescriptions, ok := ps.publishedServices[apfId]
124         if ok {
125                 err := ctx.JSON(http.StatusOK, serviceDescriptions)
126                 if err != nil {
127                         // Something really bad happened, tell Echo that our handler failed
128                         return err
129                 }
130         } else {
131                 return sendCoreError(ctx, http.StatusNotFound, "Provider not registered")
132         }
133
134         return nil
135 }
136
137 func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) error {
138         var newServiceAPIDescription publishserviceapi.ServiceAPIDescription
139         err := ctx.Bind(&newServiceAPIDescription)
140         if err != nil {
141                 return sendCoreError(ctx, http.StatusBadRequest, "Invalid format for service")
142         }
143
144         ps.lock.Lock()
145         defer ps.lock.Unlock()
146
147         registeredFuncs := ps.serviceRegister.GetAefsForPublisher(apfId)
148         for _, profile := range *newServiceAPIDescription.AefProfiles {
149                 if !slices.Contains(registeredFuncs, profile.AefId) {
150                         return sendCoreError(ctx, http.StatusNotFound, "Function not registered, "+profile.AefId)
151                 }
152         }
153
154         newId := "api_id_" + newServiceAPIDescription.ApiName
155         newServiceAPIDescription.ApiId = &newId
156
157         shouldReturn, returnValue := ps.installHelmChart(newServiceAPIDescription, err, ctx, newId)
158         if shouldReturn {
159                 return returnValue
160         }
161
162         _, ok := ps.publishedServices[apfId]
163         if ok {
164                 ps.publishedServices[apfId] = append(ps.publishedServices[apfId], &newServiceAPIDescription)
165         } else {
166                 ps.publishedServices[apfId] = append([]*publishserviceapi.ServiceAPIDescription{}, &newServiceAPIDescription)
167         }
168
169         uri := ctx.Request().Host + ctx.Request().URL.String()
170         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, *newServiceAPIDescription.ApiId))
171         err = ctx.JSON(http.StatusCreated, newServiceAPIDescription)
172         if err != nil {
173                 // Something really bad happened, tell Echo that our handler failed
174                 return err
175         }
176
177         return nil
178 }
179
180 func (ps *PublishService) installHelmChart(newServiceAPIDescription publishserviceapi.ServiceAPIDescription, err error, ctx echo.Context, newId string) (bool, error) {
181         info := strings.Split(*newServiceAPIDescription.Description, ",")
182         if len(info) == 5 {
183                 err = ps.helmManager.InstallHelmChart(info[1], info[2], info[3], info[4])
184                 if err != nil {
185                         return true, sendCoreError(ctx, http.StatusBadRequest, "Unable to install Helm chart due to: "+err.Error())
186                 }
187                 log.Info("Installed service: ", newId)
188         }
189         return false, nil
190 }
191
192 func (ps *PublishService) DeleteApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
193         serviceDescriptions, ok := ps.publishedServices[string(apfId)]
194         if ok {
195                 pos, description := getServiceDescription(serviceApiId, serviceDescriptions)
196                 if description != nil {
197                         info := strings.Split(*description.Description, ",")
198                         if len(info) == 5 {
199                                 ps.helmManager.UninstallHelmChart(info[1], info[3])
200                                 log.Info("Deleted service: ", serviceApiId)
201                         }
202                         ps.lock.Lock()
203                         defer ps.lock.Unlock()
204                         ps.publishedServices[string(apfId)] = removeServiceDescription(pos, serviceDescriptions)
205                 }
206         }
207         return ctx.NoContent(http.StatusNoContent)
208 }
209
210 func (ps *PublishService) GetApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
211         ps.lock.Lock()
212         defer ps.lock.Unlock()
213
214         serviceDescriptions, ok := ps.publishedServices[apfId]
215         if ok {
216                 _, serviceDescription := getServiceDescription(serviceApiId, serviceDescriptions)
217                 if serviceDescription == nil {
218                         return ctx.NoContent(http.StatusNotFound)
219                 }
220                 err := ctx.JSON(http.StatusOK, serviceDescription)
221                 if err != nil {
222                         // Something really bad happened, tell Echo that our handler failed
223                         return err
224                 }
225
226                 return nil
227         }
228         return ctx.NoContent(http.StatusNotFound)
229 }
230
231 func getServiceDescription(serviceApiId string, descriptions []*publishserviceapi.ServiceAPIDescription) (int, *publishserviceapi.ServiceAPIDescription) {
232         for pos, description := range descriptions {
233                 if serviceApiId == *description.ApiId {
234                         return pos, description
235                 }
236         }
237         return -1, nil
238 }
239
240 func removeServiceDescription(i int, a []*publishserviceapi.ServiceAPIDescription) []*publishserviceapi.ServiceAPIDescription {
241         a[i] = a[len(a)-1] // Copy last element to index i.
242         a[len(a)-1] = nil  // Erase last element (write zero value).
243         a = a[:len(a)-1]   // Truncate slice.
244         return a
245 }
246
247 func (ps *PublishService) ModifyIndAPFPubAPI(ctx echo.Context, apfId string, serviceApiId string) error {
248         return ctx.NoContent(http.StatusNotImplemented)
249 }
250
251 func (ps *PublishService) PutApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
252         return ctx.NoContent(http.StatusNotImplemented)
253 }
254
255 // This function wraps sending of an error in the Error format, and
256 // handling the failure to marshal that.
257 func sendCoreError(ctx echo.Context, code int, message string) error {
258         pd := common29122.ProblemDetails{
259                 Cause:  &message,
260                 Status: &code,
261         }
262         err := ctx.JSON(code, pd)
263         return err
264 }