Improve locking
[nonrtric/plt/sme.git] / capifcore / internal / providermanagement / providermanagement.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 providermanagement
22
23 import (
24         "fmt"
25         "net/http"
26         "path"
27         "strings"
28         "sync"
29
30         "github.com/labstack/echo/v4"
31
32         "oransc.org/nonrtric/capifcore/internal/common29122"
33         provapi "oransc.org/nonrtric/capifcore/internal/providermanagementapi"
34
35         log "github.com/sirupsen/logrus"
36 )
37
38 //go:generate mockery --name ServiceRegister
39 type ServiceRegister interface {
40         IsFunctionRegistered(functionId string) bool
41         GetAefsForPublisher(apfId string) []string
42 }
43
44 type ProviderManager struct {
45         registeredProviders map[string]provapi.APIProviderEnrolmentDetails
46         lock                sync.Mutex
47 }
48
49 func NewProviderManager() *ProviderManager {
50         return &ProviderManager{
51                 registeredProviders: make(map[string]provapi.APIProviderEnrolmentDetails),
52         }
53 }
54
55 func (pm *ProviderManager) IsFunctionRegistered(functionId string) bool {
56         registered := false
57 out:
58         for _, provider := range pm.registeredProviders {
59                 for _, registeredFunc := range *provider.ApiProvFuncs {
60                         if *registeredFunc.ApiProvFuncId == functionId {
61                                 registered = true
62                                 break out
63                         }
64                 }
65         }
66
67         return registered
68 }
69
70 func (pm *ProviderManager) GetAefsForPublisher(apfId string) []string {
71         for _, provider := range pm.registeredProviders {
72                 for _, registeredFunc := range *provider.ApiProvFuncs {
73                         if *registeredFunc.ApiProvFuncId == apfId && registeredFunc.ApiProvFuncRole == provapi.ApiProviderFuncRoleAPF {
74                                 return getExposedFuncs(provider.ApiProvFuncs)
75                         }
76                 }
77         }
78         return nil
79 }
80
81 func getExposedFuncs(providerFuncs *[]provapi.APIProviderFunctionDetails) []string {
82         exposedFuncs := []string{}
83         for _, registeredFunc := range *providerFuncs {
84                 if registeredFunc.ApiProvFuncRole == provapi.ApiProviderFuncRoleAEF {
85                         exposedFuncs = append(exposedFuncs, *registeredFunc.ApiProvFuncId)
86                 }
87         }
88         return exposedFuncs
89 }
90
91 func (pm *ProviderManager) PostRegistrations(ctx echo.Context) error {
92         var newProvider provapi.APIProviderEnrolmentDetails
93         err := ctx.Bind(&newProvider)
94         if err != nil {
95                 return sendCoreError(ctx, http.StatusBadRequest, "Invalid format for provider")
96         }
97
98         if newProvider.ApiProvDomInfo == nil || *newProvider.ApiProvDomInfo == "" {
99                 return sendCoreError(ctx, http.StatusBadRequest, "Provider missing required ApiProvDomInfo")
100         }
101
102         pm.prepareNewProvider(&newProvider)
103
104         uri := ctx.Request().Host + ctx.Request().URL.String()
105         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, *newProvider.ApiProvDomId))
106         err = ctx.JSON(http.StatusCreated, newProvider)
107         if err != nil {
108                 // Something really bad happened, tell Echo that our handler failed
109                 return err
110         }
111
112         return nil
113 }
114
115 func (pm *ProviderManager) prepareNewProvider(newProvider *provapi.APIProviderEnrolmentDetails) {
116         pm.lock.Lock()
117         defer pm.lock.Unlock()
118
119         newProvider.ApiProvDomId = pm.getDomainId(newProvider.ApiProvDomInfo)
120
121         pm.registerFunctions(newProvider.ApiProvFuncs)
122         pm.registeredProviders[*newProvider.ApiProvDomId] = *newProvider
123 }
124
125 func (pm *ProviderManager) DeleteRegistrationsRegistrationId(ctx echo.Context, registrationId string) error {
126
127         log.Debug(pm.registeredProviders)
128         if _, ok := pm.registeredProviders[registrationId]; ok {
129                 pm.deleteProvider(registrationId)
130         }
131
132         return ctx.NoContent(http.StatusNoContent)
133 }
134
135 func (pm *ProviderManager) deleteProvider(registrationId string) {
136         log.Debug("Deleting provider", registrationId)
137         pm.lock.Lock()
138         defer pm.lock.Unlock()
139         delete(pm.registeredProviders, registrationId)
140 }
141
142 func (pm *ProviderManager) PutRegistrationsRegistrationId(ctx echo.Context, registrationId string) error {
143         errMsg := "Unable to update provider due to %s."
144         registeredProvider, err := pm.checkIfProviderIsRegistered(registrationId, ctx)
145         if err != nil {
146                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
147         }
148
149         updatedProvider, err := getProviderFromRequest(ctx)
150         if err != nil {
151                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
152         }
153
154         err = pm.updateProvider(updatedProvider, registeredProvider)
155         if err != nil {
156                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
157         }
158
159         err = ctx.JSON(http.StatusOK, *registeredProvider)
160         if err != nil {
161                 // Something really bad happened, tell Echo that our handler failed
162                 return err
163         }
164
165         return nil
166 }
167
168 func (pm *ProviderManager) checkIfProviderIsRegistered(registrationId string, ctx echo.Context) (*provapi.APIProviderEnrolmentDetails, error) {
169         registeredProvider, ok := pm.registeredProviders[registrationId]
170         if !ok {
171                 return nil, fmt.Errorf("provider not onboarded")
172         }
173         return &registeredProvider, nil
174 }
175
176 func getProviderFromRequest(ctx echo.Context) (provapi.APIProviderEnrolmentDetails, error) {
177         var updatedProvider provapi.APIProviderEnrolmentDetails
178         err := ctx.Bind(&updatedProvider)
179         if err != nil {
180                 return provapi.APIProviderEnrolmentDetails{}, fmt.Errorf("invalid format for provider")
181         }
182         return updatedProvider, nil
183 }
184
185 func (pm *ProviderManager) updateProvider(updatedProvider provapi.APIProviderEnrolmentDetails, registeredProvider *provapi.APIProviderEnrolmentDetails) error {
186         pm.lock.Lock()
187         defer pm.lock.Unlock()
188
189         updateDomainInfo(&updatedProvider, registeredProvider)
190
191         funcsAfterUpdate, err := updateFuncs(updatedProvider.ApiProvFuncs, registeredProvider.ApiProvFuncs)
192         if err == nil {
193                 registeredProvider.ApiProvFuncs = funcsAfterUpdate
194
195                 pm.registeredProviders[*registeredProvider.ApiProvDomId] = *registeredProvider
196                 return nil
197         }
198         return err
199 }
200
201 func updateDomainInfo(updatedProvider, registeredProvider *provapi.APIProviderEnrolmentDetails) {
202         if updatedProvider.ApiProvDomInfo != nil {
203                 registeredProvider.ApiProvDomInfo = updatedProvider.ApiProvDomInfo
204         }
205 }
206
207 func updateFuncs(updatedFuncs, registeredFuncs *[]provapi.APIProviderFunctionDetails) (*[]provapi.APIProviderFunctionDetails, error) {
208         addedFuncs := []provapi.APIProviderFunctionDetails{}
209         changedFuncs := []provapi.APIProviderFunctionDetails{}
210         for _, function := range *updatedFuncs {
211                 if function.ApiProvFuncId == nil {
212                         function.ApiProvFuncId = getFuncId(function.ApiProvFuncRole, function.ApiProvFuncInfo)
213                         addedFuncs = append(addedFuncs, function)
214                 } else {
215                         registeredFunction, ok := getApiFunc(*function.ApiProvFuncId, registeredFuncs)
216                         if !ok {
217                                 return nil, fmt.Errorf("function with ID %s is not registered for the provider", *function.ApiProvFuncId)
218                         }
219                         if function.ApiProvFuncInfo != nil {
220                                 registeredFunction.ApiProvFuncInfo = function.ApiProvFuncInfo
221                         }
222                         changedFuncs = append(changedFuncs, function)
223                 }
224         }
225         modifiedFuncs := append(changedFuncs, addedFuncs...)
226         return &modifiedFuncs, nil
227 }
228
229 func getApiFunc(funcId string, apiFunctions *[]provapi.APIProviderFunctionDetails) (provapi.APIProviderFunctionDetails, bool) {
230         for _, function := range *apiFunctions {
231                 if *function.ApiProvFuncId == funcId {
232                         return function, true
233                 }
234         }
235         return provapi.APIProviderFunctionDetails{}, false
236 }
237
238 func (pm *ProviderManager) ModifyIndApiProviderEnrolment(ctx echo.Context, registrationId string) error {
239         return ctx.NoContent(http.StatusNotImplemented)
240 }
241
242 func (pm *ProviderManager) registerFunctions(provFuncs *[]provapi.APIProviderFunctionDetails) {
243         if provFuncs == nil {
244                 return
245         }
246         for i, provFunc := range *provFuncs {
247                 (*provFuncs)[i].ApiProvFuncId = getFuncId(provFunc.ApiProvFuncRole, provFunc.ApiProvFuncInfo)
248         }
249 }
250
251 func (pm *ProviderManager) getDomainId(domainInfo *string) *string {
252         idAsString := "domain_id_" + strings.ReplaceAll(*domainInfo, " ", "_")
253         return &idAsString
254 }
255
256 func getFuncId(role provapi.ApiProviderFuncRole, funcInfo *string) *string {
257         var idPrefix string
258         switch role {
259         case provapi.ApiProviderFuncRoleAPF:
260                 idPrefix = "APF_id_"
261         case provapi.ApiProviderFuncRoleAMF:
262                 idPrefix = "AMF_id_"
263         case provapi.ApiProviderFuncRoleAEF:
264                 idPrefix = "AEF_id_"
265         default:
266                 idPrefix = "function_id_"
267         }
268         idAsString := idPrefix + strings.ReplaceAll(*funcInfo, " ", "_")
269         return &idAsString
270 }
271
272 // This function wraps sending of an error in the Error format, and
273 // handling the failure to marshal that.
274 func sendCoreError(ctx echo.Context, code int, message string) error {
275         pd := common29122.ProblemDetails{
276                 Cause:  &message,
277                 Status: &code,
278         }
279         err := ctx.JSON(code, pd)
280         return err
281 }