2 // ========================LICENSE_START=================================
5 // Copyright (C) 2023-2024: OpenInfra Foundation Europe
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
11 // http://www.apache.org/licenses/LICENSE-2.0
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===================================
21 package publishserviceapi
30 resty "github.com/go-resty/resty/v2"
31 log "github.com/sirupsen/logrus"
33 common29122 "oransc.org/nonrtric/servicemanager/internal/common29122"
36 func (sd *ServiceAPIDescription) PrepareNewService() {
37 apiName := "api_id_" + strings.ReplaceAll(sd.ApiName, " ", "_")
41 func (sd *ServiceAPIDescription) RegisterKong(kongDomain string,
43 kongIPv4 common29122.Ipv4Addr,
44 kongDataPlanePort common29122.Port,
45 kongControlPlanePort common29122.Port,
46 apfId string) (int, error) {
48 log.Trace("entering RegisterKong")
53 kongControlPlaneURL := fmt.Sprintf("%s://%s:%d", kongProtocol, kongIPv4, kongControlPlanePort)
55 statusCode, err = sd.createKongRoutes(kongControlPlaneURL, apfId)
56 if (err != nil) || (statusCode != http.StatusCreated) {
57 return statusCode, err
60 sd.updateInterfaceDescription(kongIPv4, kongDataPlanePort, kongDomain)
62 log.Trace("exiting from RegisterKong")
63 return statusCode, nil
66 func (sd *ServiceAPIDescription) createKongRoutes(kongControlPlaneURL string, apfId string) (int, error) {
67 log.Trace("entering createKongRoutes")
75 profiles := *sd.AefProfiles
76 for _, profile := range profiles {
77 log.Debugf("createKongRoutes, AefId %s", profile.AefId)
78 for _, version := range profile.Versions {
79 log.Debugf("createKongRoutes, apiVersion \"%s\"", version.ApiVersion)
80 for _, resource := range *version.Resources {
81 statusCode, err = sd.createKongRoute(kongControlPlaneURL, client, resource, apfId, profile.AefId, version.ApiVersion)
82 if (err != nil) || (statusCode != http.StatusCreated) {
83 return statusCode, err
88 return statusCode, nil
91 func (sd *ServiceAPIDescription) createKongRoute(
92 kongControlPlaneURL string,
97 apiVersion string ) (int, error) {
98 log.Trace("entering createKongRoute")
100 resourceName := resource.ResourceName
103 tags := buildTags(apfId, aefId, apiId, apiVersion, resourceName)
104 log.Debugf("createKongRoute, tags %s", tags)
106 serviceName := apiId + "_" + resourceName
107 routeName := serviceName
109 log.Debugf("createKongRoute, serviceName %s", serviceName)
110 log.Debugf("createKongRoute, routeName %s", routeName)
111 log.Debugf("createKongRoute, aefId %s", aefId)
113 uri := buildUriWithVersion(apiVersion, resource.Uri)
114 log.Debugf("createKongRoute, uri %s", uri)
116 statusCode, err := sd.createKongService(kongControlPlaneURL, serviceName, uri, tags)
117 if (err != nil) || (statusCode != http.StatusCreated) {
118 return statusCode, err
121 kongRoutesURL := kongControlPlaneURL + "/services/" + serviceName + "/routes"
123 // Define the route information for Kong
124 kongRouteInfo := map[string]interface{}{
126 "paths": []string{uri},
127 "methods": resource.Operations,
132 // Make the POST request to create the Kong service
133 resp, err := client.R().
134 SetHeader("Content-Type", "application/json").
135 SetBody(kongRouteInfo).
138 // Check for errors in the request
140 log.Debugf("createKongRoute POST Error: %v", err)
141 return resp.StatusCode(), err
144 // Check the response status code
145 if resp.StatusCode() == http.StatusCreated {
146 log.Infof("kong route %s created successfully", routeName)
148 log.Debugf("kongRoutesURL %s", kongRoutesURL)
149 err = fmt.Errorf("error creating Kong route. Status code: %d", resp.StatusCode())
150 log.Error(err.Error())
151 log.Errorf("response body: %s", resp.Body())
152 return resp.StatusCode(), err
155 return resp.StatusCode(), nil
158 func buildUriWithVersion(apiVersion string, uri string) string {
159 if apiVersion != "" {
160 if apiVersion[0] != '/' {
161 apiVersion = "/" + apiVersion
163 if apiVersion[len(apiVersion)-1] != '/' && uri[0] != '/' {
164 apiVersion = apiVersion + "/"
166 uri = apiVersion + uri
171 func buildTags(apfId string, aefId string, apiId string, apiVersion string, resourceName string) []string {
172 tagsMap := map[string]string{
176 "apiVersion": apiVersion,
177 "resourceName": resourceName,
180 // Convert the map to a slice of strings
181 var tagsSlice []string
182 for key, value := range tagsMap {
183 str := fmt.Sprintf("%s: %s", key, value)
184 tagsSlice = append(tagsSlice, str)
190 func (sd *ServiceAPIDescription) createKongService(kongControlPlaneURL string, kongServiceName string, kongServiceUri string, tags []string) (int, error) {
191 log.Tracef("entering createKongService")
192 log.Tracef("createKongService, kongServiceName %s", kongServiceName)
194 // Define the service information for Kong
195 firstAEFProfileIpv4Addr, firstAEFProfilePort, err := sd.findFirstAEFProfile()
197 return http.StatusBadRequest, err
200 kongControlPlaneURLParsed, err := url.Parse(kongControlPlaneURL)
202 return http.StatusInternalServerError, err
204 log.Debugf("kongControlPlaneURL %s", kongControlPlaneURL)
205 log.Debugf("kongControlPlaneURLParsed.Scheme %s", kongControlPlaneURLParsed.Scheme)
207 kongServiceInfo := map[string]interface{}{
208 "host": firstAEFProfileIpv4Addr,
209 "name": kongServiceName,
210 "port": firstAEFProfilePort,
211 "protocol": kongControlPlaneURLParsed.Scheme,
212 "path": kongServiceUri,
216 // Kong admin API endpoint for creating a service
217 kongServicesURL := kongControlPlaneURL + "/services"
219 // Create a new Resty client
220 client := resty.New()
222 // Make the POST request to create the Kong service
223 resp, err := client.R().
224 SetHeader("Content-Type", "application/json").
225 SetBody(kongServiceInfo).
226 Post(kongServicesURL)
228 // Check for errors in the request
230 log.Errorf("create Kong Service Request Error: %v", err)
231 return http.StatusInternalServerError, err
234 // Check the response status code
235 statusCode := resp.StatusCode()
236 if statusCode == http.StatusCreated {
237 log.Infof("kong service %s created successfully", kongServiceName)
238 } else if resp.StatusCode() == http.StatusConflict {
239 log.Errorf("kong service already exists. Status code: %d", resp.StatusCode())
240 err = fmt.Errorf("service with identical apiName is already published") // for compatibilty with Capif error message on a duplicate service
241 statusCode = http.StatusForbidden // for compatibilty with the spec, TS29222_CAPIF_Publish_Service_API
243 err = fmt.Errorf("error creating Kong service. Status code: %d", resp.StatusCode())
246 log.Errorf(err.Error())
247 log.Errorf("response body: %s", resp.Body())
250 return statusCode, err
253 func (sd *ServiceAPIDescription) findFirstAEFProfile() (common29122.Ipv4Addr, common29122.Port, error) {
254 log.Tracef("entering findFirstAEFProfile")
255 var aefProfile AefProfile
256 if *sd.AefProfiles != nil {
257 aefProfile = (*sd.AefProfiles)[0]
259 if (*sd.AefProfiles == nil) || (aefProfile.InterfaceDescriptions == nil) {
260 err := errors.New("cannot read interfaceDescription")
261 log.Errorf(err.Error())
262 return "", common29122.Port(0), err
265 interfaceDescription := (*aefProfile.InterfaceDescriptions)[0]
266 firstIpv4Addr := *interfaceDescription.Ipv4Addr
267 firstPort := *interfaceDescription.Port
269 log.Debugf("findFirstAEFProfile firstIpv4Addr %s firstPort %d", firstIpv4Addr, firstPort)
271 return firstIpv4Addr, firstPort, nil
274 // Update our exposures to point to Kong by replacing in incoming interface description with Kong interface descriptions.
275 func (sd *ServiceAPIDescription) updateInterfaceDescription(kongIPv4 common29122.Ipv4Addr, kongDataPlanePort common29122.Port, kongDomain string) {
276 log.Trace("updating InterfaceDescriptions")
277 interfaceDesc := InterfaceDescription{
279 Port: &kongDataPlanePort,
281 interfaceDescs := []InterfaceDescription{interfaceDesc}
283 profiles := *sd.AefProfiles
284 for i, profile := range profiles {
285 profile.DomainName = &kongDomain
286 profile.InterfaceDescriptions = &interfaceDescs
287 profiles[i] = profile
291 func (sd *ServiceAPIDescription) UnregisterKong(kongDomain string, kongProtocol string, kongIPv4 common29122.Ipv4Addr, kongDataPlanePort common29122.Port, kongControlPlanePort common29122.Port) (int, error) {
292 log.Trace("entering UnregisterKong")
298 kongControlPlaneURL := fmt.Sprintf("%s://%s:%d", kongProtocol, kongIPv4, kongControlPlanePort)
300 statusCode, err = sd.deleteKongRoutes(kongControlPlaneURL)
301 if (err != nil) || (statusCode != http.StatusNoContent) {
302 return statusCode, err
305 log.Trace("exiting from UnregisterKong")
306 return statusCode, nil
309 func (sd *ServiceAPIDescription) deleteKongRoutes(kongControlPlaneURL string) (int, error) {
310 log.Trace("entering deleteKongRoutes")
317 client := resty.New()
319 profiles := *sd.AefProfiles
320 for _, profile := range profiles {
321 log.Debugf("deleteKongRoutes, AefId %s", profile.AefId)
322 for _, version := range profile.Versions {
323 log.Debugf("deleteKongRoutes, apiVersion \"%s\"", version.ApiVersion)
324 for _, resource := range *version.Resources {
325 statusCode, err = sd.deleteKongRoute(kongControlPlaneURL, client, resource, profile.AefId, version.ApiVersion)
326 if (err != nil) || (statusCode != http.StatusNoContent) {
327 return statusCode, err
332 return statusCode, nil
335 func (sd *ServiceAPIDescription) deleteKongRoute(kongControlPlaneURL string, client *resty.Client, resource Resource, aefId string, apiVersion string) (int, error) {
336 log.Trace("entering deleteKongRoute")
337 routeName := *sd.ApiId + "_" + resource.ResourceName
338 kongRoutesURL := kongControlPlaneURL + "/routes/" + routeName + "?tags=" + aefId
339 log.Debugf("deleteKongRoute, routeName %s, tag %s", routeName, aefId)
341 // Make the DELETE request to delete the Kong route
342 resp, err := client.R().Delete(kongRoutesURL)
344 // Check for errors in the request
346 log.Errorf("error on Kong route delete: %v", err)
347 return resp.StatusCode(), err
350 // Check the response status code
351 if resp.StatusCode() == http.StatusNoContent {
352 log.Infof("kong route %s deleted successfully", routeName)
354 log.Debugf("kongRoutesURL: %s", kongRoutesURL)
355 log.Errorf("error deleting Kong route. Status code: %d", resp.StatusCode())
356 log.Errorf("response body: %s", resp.Body())
357 return resp.StatusCode(), err
360 statusCode, err := sd.deleteKongService(kongControlPlaneURL, routeName, aefId)
361 if (err != nil) || (statusCode != http.StatusNoContent) {
362 return statusCode, err
364 return statusCode, err
367 func (sd *ServiceAPIDescription) deleteKongService(kongControlPlaneURL string, serviceName string, aefId string) (int, error) {
368 log.Trace("entering deleteKongService")
369 // Define the service information for Kong
370 // Kong admin API endpoint for deleting a service
371 kongServicesURL := kongControlPlaneURL + "/services/" + serviceName + "?tags=" + aefId
373 // Create a new Resty client
374 client := resty.New()
376 // Make the DELETE request to delete the Kong service
377 resp, err := client.R().
378 SetHeader("Content-Type", "application/json").
379 Delete(kongServicesURL)
381 // Check for errors in the request
383 log.Errorf("delete kong service request: %v", err)
384 return http.StatusInternalServerError, err
387 // Check the response status code
388 if resp.StatusCode() == http.StatusNoContent {
389 log.Infof("kong service %s deleted successfully", serviceName)
391 log.Debugf("kongServicesURL: %s", kongServicesURL)
392 log.Errorf("deleting Kong service, status code: %d", resp.StatusCode())
393 log.Errorf("response body: %s", resp.Body())
395 return resp.StatusCode(), nil