NONRTRIC-944: ServiceManager - Add kongclearup to Docker image
[nonrtric/plt/sme.git] / servicemanager / internal / publishservice / publishservice.go
1 // -
2 //   ========================LICENSE_START=================================
3 //   O-RAN-SC
4 //   %%
5 //   Copyright (C) 2023-2024: OpenInfra Foundation Europe
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         "context"
25         "fmt"
26         "net/http"
27         "path"
28
29         echo "github.com/labstack/echo/v4"
30         log "github.com/sirupsen/logrus"
31
32         "oransc.org/nonrtric/servicemanager/internal/common29122"
33         publishapi "oransc.org/nonrtric/servicemanager/internal/publishserviceapi"
34 )
35
36 type PublishService struct {
37         KongDomain                              string;
38         KongProtocol                    string;
39         KongIPv4                        common29122.Ipv4Addr;
40         KongDataPlanePort               common29122.Port;
41         KongControlPlanePort    common29122.Port;
42         CapifProtocol                   string;
43         CapifIPv4                       common29122.Ipv4Addr;
44         CapifPort                               common29122.Port;
45 }
46
47 // Creates a service that implements both the PublishRegister and the publishserviceapi.ServerInterface interfaces.
48 func NewPublishService(kongDomain string, kongProtocol string, kongIPv4 common29122.Ipv4Addr, kongDataPlanePort common29122.Port, kongControlPlanePort common29122.Port, capifProtocol string, capifIPv4 common29122.Ipv4Addr, capifPort common29122.Port) *PublishService {
49         return &PublishService{
50                 KongDomain                              : kongDomain,
51                 KongProtocol                    : kongProtocol,
52                 KongIPv4                                : kongIPv4,
53                 KongDataPlanePort               : kongDataPlanePort,
54                 KongControlPlanePort    : kongControlPlanePort,
55                 CapifProtocol                   : capifProtocol,
56                 CapifIPv4                               : capifIPv4,
57                 CapifPort                               : capifPort,
58         }
59 }
60
61 // Publish a new API.
62 func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) error {
63         log.Tracef("entering PostApfIdServiceApis apfId %s", apfId)
64
65         capifcoreUrl := fmt.Sprintf("%s://%s:%d/published-apis/v1/", ps.CapifProtocol, ps.CapifIPv4, ps.CapifPort)
66         client, err := publishapi.NewClientWithResponses(capifcoreUrl)
67         if err != nil {
68                 return err
69         }
70
71         var (
72                 ctxHandler context.Context
73                 cancel     context.CancelFunc
74         )
75         ctxHandler, cancel = context.WithCancel(context.Background())
76         defer cancel()
77
78         newServiceAPIDescription, err := getServiceFromRequest(ctx)
79         if err != nil {
80                 return err
81         }
82
83         newServiceAPIDescription.PrepareNewService()
84
85         statusCode, err := newServiceAPIDescription.RegisterKong(ps.KongDomain, ps.KongProtocol, ps.KongIPv4, ps.KongDataPlanePort, ps.KongControlPlanePort, apfId)
86         if (err != nil) || (statusCode != http.StatusCreated) {
87                 // We can return with http.StatusForbidden if there is a http.StatusConflict detected by Kong
88                 msg := err.Error()
89                 log.Errorf("error on RegisterKong %s", msg)
90                 return sendCoreError(ctx, statusCode, msg)
91         }
92
93         bodyServiceAPIDescription := publishapi.PostApfIdServiceApisJSONRequestBody(newServiceAPIDescription)
94         var rsp *publishapi.PostApfIdServiceApisResponse
95
96         log.Trace("calling PostApfIdServiceApisWithResponse")
97         rsp, err = client.PostApfIdServiceApisWithResponse(ctxHandler, apfId, bodyServiceAPIDescription)
98
99         if err != nil {
100                 msg := err.Error()
101                 log.Errorf("error on PostApfIdServiceApisWithResponse %s", msg)
102                 return sendCoreError(ctx, http.StatusInternalServerError, msg)
103         }
104
105         if rsp.StatusCode() != http.StatusCreated {
106                 msg := string(rsp.Body)
107                 log.Debugf("PostApfIdServiceApisWithResponse status code %d", rsp.StatusCode())
108                 log.Debugf("PostApfIdServiceApisWithResponse error %s", msg)
109                 if rsp.StatusCode() == http.StatusForbidden || rsp.StatusCode() == http.StatusBadRequest {
110                         newServiceAPIDescription.UnregisterKong(ps.KongDomain, ps.KongProtocol, ps.KongIPv4, ps.KongDataPlanePort, ps.KongControlPlanePort)
111                 }
112                 return sendCoreError(ctx, rsp.StatusCode(), msg)
113         }
114
115         rspServiceAPIDescription := *rsp.JSON201
116         apiId := *rspServiceAPIDescription.ApiId
117
118         uri := ctx.Request().Host + ctx.Request().URL.String()
119         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, apiId))
120
121         err = ctx.JSON(http.StatusCreated, rspServiceAPIDescription)
122         if err != nil {
123                 return err // Tell Echo that our handler failed
124         }
125
126         return nil
127 }
128
129
130 // Unpublish a published service API.
131 func (ps *PublishService) DeleteApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
132         log.Tracef("entering DeleteApfIdServiceApisServiceApiId apfId %s serviceApiId %s", apfId, serviceApiId)
133
134         capifcoreUrl := fmt.Sprintf("%s://%s:%d/published-apis/v1/", ps.CapifProtocol, ps.CapifIPv4, ps.CapifPort)
135         client, err := publishapi.NewClientWithResponses(capifcoreUrl)
136         if err != nil {
137                 return err
138         }
139
140         var (
141                 ctxHandler context.Context
142                 cancel     context.CancelFunc
143         )
144         ctxHandler, cancel = context.WithCancel(context.Background())
145         defer cancel()
146
147         log.Debugf("call GetApfIdServiceApisServiceApiIdWithResponse before delete apfId %s serviceApiId %s", apfId, serviceApiId)
148         var rsp *publishapi.GetApfIdServiceApisServiceApiIdResponse
149         rsp, err = client.GetApfIdServiceApisServiceApiIdWithResponse(ctxHandler, apfId, serviceApiId)
150
151         if err != nil {
152                 msg := err.Error()
153                 log.Errorf("error on GetApfIdServiceApisServiceApiIdWithResponse %s", msg)
154                 return sendCoreError(ctx, http.StatusInternalServerError, msg)
155         }
156
157         statusCode := rsp.StatusCode()
158         if statusCode != http.StatusOK {
159                 log.Debugf("GetApfIdServiceApisServiceApiIdWithResponse status %d", statusCode)
160                 return ctx.NoContent(statusCode)
161         }
162
163         rspServiceAPIDescription := *rsp.JSON200
164         statusCode, err = rspServiceAPIDescription.UnregisterKong(ps.KongDomain, ps.KongProtocol, ps.KongIPv4, ps.KongDataPlanePort, ps.KongControlPlanePort)
165         if (err != nil) || (statusCode != http.StatusNoContent) {
166                 msg := err.Error()
167                 log.Errorf("error on UnregisterKong %s", msg)
168                 return sendCoreError(ctx, statusCode, msg)
169         }
170
171         log.Trace("call DeleteApfIdServiceApisServiceApiIdWithResponse")
172         _, err = client.DeleteApfIdServiceApisServiceApiIdWithResponse(ctxHandler, apfId, serviceApiId)
173
174         if err != nil {
175                 msg := err.Error()
176                 log.Errorf("error on DeleteApfIdServiceApisServiceApiIdWithResponse %s", msg)
177                 return sendCoreError(ctx, http.StatusInternalServerError, msg)
178         }
179
180         return ctx.NoContent(http.StatusNoContent)
181 }
182
183 // Retrieve all published APIs.
184 func (ps *PublishService) GetApfIdServiceApis(ctx echo.Context, apfId string) error {
185         log.Tracef("entering GetApfIdServiceApis apfId %s", apfId)
186
187         capifcoreUrl := fmt.Sprintf("%s://%s:%d/published-apis/v1/", ps.CapifProtocol, ps.CapifIPv4, ps.CapifPort)
188         client, err := publishapi.NewClientWithResponses(capifcoreUrl)
189         if err != nil {
190                 return err
191         }
192
193         var (
194                 ctxHandler context.Context
195                 cancel     context.CancelFunc
196         )
197         ctxHandler, cancel = context.WithCancel(context.Background())
198         defer cancel()
199
200         var rsp *publishapi.GetApfIdServiceApisResponse
201         rsp, err = client.GetApfIdServiceApisWithResponse(ctxHandler, apfId)
202
203         if err != nil {
204                 msg := err.Error()
205                 log.Errorf("error on GetApfIdServiceApisWithResponse %s", msg)
206                 return sendCoreError(ctx, http.StatusInternalServerError, msg)
207         }
208
209         if rsp.StatusCode() != http.StatusOK {
210                 msg := string(rsp.Body)
211                 log.Errorf("GetApfIdServiceApisWithResponse status %d", rsp.StatusCode())
212                 log.Errorf("GetApfIdServiceApisWithResponse error %s", msg)
213                 return sendCoreError(ctx, rsp.StatusCode(), msg)
214         }
215
216         rspServiceAPIDescriptions := *rsp.JSON200
217         err = ctx.JSON(rsp.StatusCode(), rspServiceAPIDescriptions)
218         if err != nil {
219                 return err // tell Echo that our handler failed
220         }
221         return nil
222 }
223
224 // Retrieve a published service API.
225 func (ps *PublishService) GetApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
226         log.Tracef("entering GetApfIdServiceApisServiceApiId apfId %s", apfId)
227
228         capifcoreUrl := fmt.Sprintf("%s://%s:%d/published-apis/v1/", ps.CapifProtocol, ps.CapifIPv4, ps.CapifPort)
229         client, err := publishapi.NewClientWithResponses(capifcoreUrl)
230         if err != nil {
231                 return err
232         }
233
234         var (
235                 ctxHandler context.Context
236                 cancel     context.CancelFunc
237         )
238         ctxHandler, cancel = context.WithCancel(context.Background())
239         defer cancel()
240
241         var rsp *publishapi.GetApfIdServiceApisServiceApiIdResponse
242         rsp, err = client.GetApfIdServiceApisServiceApiIdWithResponse(ctxHandler, apfId, serviceApiId)
243
244         if err != nil {
245                 msg := err.Error()
246                 log.Errorf("error on GetApfIdServiceApisServiceApiIdWithResponse %s", msg)
247                 return sendCoreError(ctx, http.StatusInternalServerError, msg)
248         }
249
250         statusCode := rsp.StatusCode()
251         if statusCode != http.StatusOK {
252                 return ctx.NoContent(statusCode)
253         }
254
255         rspServiceAPIDescription := *rsp.JSON200
256
257         err = ctx.JSON(http.StatusOK, rspServiceAPIDescription)
258         if err != nil {
259                 return err // tell Echo that our handler failed
260         }
261         return nil
262 }
263
264 // Modify an existing published service API.
265 func (ps *PublishService) ModifyIndAPFPubAPI(ctx echo.Context, apfId string, serviceApiId string) error {
266         return ctx.NoContent(http.StatusNotImplemented)
267 }
268
269 // Update a published service API.
270 func (ps *PublishService) PutApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
271         log.Tracef("entering PutApfIdServiceApisServiceApiId apfId %s", apfId)
272
273         capifcoreUrl := fmt.Sprintf("%s://%s:%d/published-apis/v1/", ps.CapifProtocol, ps.CapifIPv4, ps.CapifPort)
274         client, err := publishapi.NewClientWithResponses(capifcoreUrl)
275         if err != nil {
276                 return err
277         }
278
279         var (
280                 ctxHandler context.Context
281                 cancel     context.CancelFunc
282         )
283         ctxHandler, cancel = context.WithCancel(context.Background())
284         defer cancel()
285
286         updatedServiceDescription, err := getServiceFromRequest(ctx)
287         if err != nil {
288                 return err
289         }
290
291         var rsp *publishapi.PutApfIdServiceApisServiceApiIdResponse
292         bodyServiceAPIDescription := publishapi.PutApfIdServiceApisServiceApiIdJSONRequestBody(updatedServiceDescription)
293
294         rsp, err = client.PutApfIdServiceApisServiceApiIdWithResponse(ctxHandler, apfId, serviceApiId, bodyServiceAPIDescription)
295
296         if err != nil {
297                 msg := err.Error()
298                 log.Errorf("error on PutApfIdServiceApisServiceApiIdWithResponse %s", msg)
299                 return sendCoreError(ctx, http.StatusInternalServerError, msg)
300         }
301
302         if rsp.StatusCode() != http.StatusOK {
303                 log.Errorf("PutApfIdServiceApisServiceApiIdWithResponse status code %d", rsp.StatusCode())
304                 if rsp.StatusCode() == http.StatusBadRequest {
305                         updatedServiceDescription.UnregisterKong(ps.KongDomain, ps.KongProtocol, ps.KongIPv4, ps.KongDataPlanePort, ps.KongControlPlanePort)
306                 }
307                 msg := string(rsp.Body)
308                 return sendCoreError(ctx, rsp.StatusCode(), msg)
309         }
310
311         rspServiceAPIDescription := *rsp.JSON200
312         apiId := *rspServiceAPIDescription.ApiId
313
314         uri := ctx.Request().Host + ctx.Request().URL.String()
315         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, apiId))
316
317         err = ctx.JSON(http.StatusOK, rspServiceAPIDescription)
318         if err != nil {
319                 return err // Tell Echo that our handler failed
320         }
321         return nil
322 }
323
324
325 func getServiceFromRequest(ctx echo.Context) (publishapi.ServiceAPIDescription, error) {
326         var updatedServiceDescription publishapi.ServiceAPIDescription
327         err := ctx.Bind(&updatedServiceDescription)
328         if err != nil {
329                 return publishapi.ServiceAPIDescription{}, fmt.Errorf("invalid format for service")
330         }
331         return updatedServiceDescription, nil
332 }
333
334 // This function wraps sending of an error in the Error format, and
335 // handling the failure to marshal that.
336 func sendCoreError(ctx echo.Context, code int, message string) error {
337         pd := common29122.ProblemDetails{
338                 Cause:  &message,
339                 Status: &code,
340         }
341         err := ctx.JSON(code, pd)
342         return err
343 }