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