e073e38de15e38c0b8635c3bf980ad69b16e6239
[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         return ctx.NoContent(http.StatusNotImplemented)
124 }
125
126 func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) error {
127         var newServiceAPIDescription publishserviceapi.ServiceAPIDescription
128         err := ctx.Bind(&newServiceAPIDescription)
129         if err != nil {
130                 return sendCoreError(ctx, http.StatusBadRequest, "Invalid format for service")
131         }
132
133         ps.lock.Lock()
134         defer ps.lock.Unlock()
135
136         registeredFuncs := ps.serviceRegister.GetAefsForPublisher(apfId)
137         for _, profile := range *newServiceAPIDescription.AefProfiles {
138                 if !slices.Contains(registeredFuncs, profile.AefId) {
139                         return sendCoreError(ctx, http.StatusNotFound, "Function not registered, "+profile.AefId)
140                 }
141         }
142
143         newId := "api_id_" + newServiceAPIDescription.ApiName
144         newServiceAPIDescription.ApiId = &newId
145         info := strings.Split(*newServiceAPIDescription.Description, ",")
146         if len(info) == 5 {
147                 err = ps.helmManager.InstallHelmChart(info[1], info[2], info[3], info[4])
148                 if err != nil {
149                         return sendCoreError(ctx, http.StatusBadRequest, "Unable to install Helm chart due to: "+err.Error())
150                 }
151                 log.Info("Installed service: ", newId)
152         }
153         _, ok := ps.publishedServices[apfId]
154         if ok {
155                 ps.publishedServices[apfId] = append(ps.publishedServices[apfId], &newServiceAPIDescription)
156         } else {
157                 ps.publishedServices[apfId] = append([]*publishserviceapi.ServiceAPIDescription{}, &newServiceAPIDescription)
158         }
159
160         uri := ctx.Request().Host + ctx.Request().URL.String()
161         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, *newServiceAPIDescription.ApiId))
162         err = ctx.JSON(http.StatusCreated, newServiceAPIDescription)
163         if err != nil {
164                 // Something really bad happened, tell Echo that our handler failed
165                 return err
166         }
167
168         return nil
169 }
170
171 func (ps *PublishService) DeleteApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
172         serviceDescriptions, ok := ps.publishedServices[string(apfId)]
173         if ok {
174                 pos, description := getServiceDescription(serviceApiId, serviceDescriptions)
175                 if description != nil {
176                         info := strings.Split(*description.Description, ",")
177                         if len(info) == 5 {
178                                 ps.helmManager.UninstallHelmChart(info[1], info[3])
179                                 log.Info("Deleted service: ", serviceApiId)
180                         }
181                         ps.lock.Lock()
182                         defer ps.lock.Unlock()
183                         ps.publishedServices[string(apfId)] = removeServiceDescription(pos, serviceDescriptions)
184                 }
185         }
186         return ctx.NoContent(http.StatusNoContent)
187 }
188
189 func (ps *PublishService) GetApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
190         ps.lock.Lock()
191         defer ps.lock.Unlock()
192
193         serviceDescriptions, ok := ps.publishedServices[apfId]
194         if ok {
195                 _, serviceDescription := getServiceDescription(serviceApiId, serviceDescriptions)
196                 if serviceDescription == nil {
197                         return ctx.NoContent(http.StatusNotFound)
198                 }
199                 err := ctx.JSON(http.StatusOK, serviceDescription)
200                 if err != nil {
201                         // Something really bad happened, tell Echo that our handler failed
202                         return err
203                 }
204
205                 return nil
206         }
207         return ctx.NoContent(http.StatusNotFound)
208 }
209
210 func getServiceDescription(serviceApiId string, descriptions []*publishserviceapi.ServiceAPIDescription) (int, *publishserviceapi.ServiceAPIDescription) {
211         for pos, description := range descriptions {
212                 if serviceApiId == *description.ApiId {
213                         return pos, description
214                 }
215         }
216         return -1, nil
217 }
218
219 func removeServiceDescription(i int, a []*publishserviceapi.ServiceAPIDescription) []*publishserviceapi.ServiceAPIDescription {
220         a[i] = a[len(a)-1] // Copy last element to index i.
221         a[len(a)-1] = nil  // Erase last element (write zero value).
222         a = a[:len(a)-1]   // Truncate slice.
223         return a
224 }
225
226 func (ps *PublishService) ModifyIndAPFPubAPI(ctx echo.Context, apfId string, serviceApiId string) error {
227         return ctx.NoContent(http.StatusNotImplemented)
228 }
229
230 func (ps *PublishService) PutApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
231         return ctx.NoContent(http.StatusNotImplemented)
232 }
233
234 // This function wraps sending of an error in the Error format, and
235 // handling the failure to marshal that.
236 func sendCoreError(ctx echo.Context, code int, message string) error {
237         pd := common29122.ProblemDetails{
238                 Cause:  &message,
239                 Status: &code,
240         }
241         err := ctx.JSON(code, pd)
242         return err
243 }