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