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(
44 kongControlPlaneIPv4 common29122.Ipv4Addr,
45 kongControlPlanePort common29122.Port,
46 kongDataPlaneIPv4 common29122.Ipv4Addr,
47 kongDataPlanePort common29122.Port,
48 apfId string) (int, error) {
50 log.Trace("entering RegisterKong")
51 log.Debugf("RegisterKong kongDataPlaneIPv4 %s", kongDataPlaneIPv4)
57 kongControlPlaneURL := fmt.Sprintf("%s://%s:%d", kongProtocol, kongControlPlaneIPv4, kongControlPlanePort)
59 statusCode, err = sd.createKongRoutes(kongControlPlaneURL, apfId)
60 if (err != nil) || (statusCode != http.StatusCreated) {
61 return statusCode, err
64 sd.updateInterfaceDescription(kongDataPlaneIPv4, kongDataPlanePort, kongDomain)
66 log.Trace("exiting from RegisterKong")
67 return statusCode, nil
70 func (sd *ServiceAPIDescription) createKongRoutes(kongControlPlaneURL string, apfId string) (int, error) {
71 log.Trace("entering createKongRoutes")
79 profiles := *sd.AefProfiles
80 for _, profile := range profiles {
81 log.Debugf("createKongRoutes, AefId %s", profile.AefId)
82 for _, version := range profile.Versions {
83 log.Debugf("createKongRoutes, apiVersion \"%s\"", version.ApiVersion)
84 for _, resource := range *version.Resources {
85 statusCode, err = sd.createKongRoute(kongControlPlaneURL, client, resource, apfId, profile.AefId, version.ApiVersion)
86 if (err != nil) || (statusCode != http.StatusCreated) {
87 return statusCode, err
92 return statusCode, nil
95 func (sd *ServiceAPIDescription) createKongRoute(
96 kongControlPlaneURL string,
101 apiVersion string ) (int, error) {
102 log.Trace("entering createKongRoute")
104 resourceName := resource.ResourceName
107 tags := buildTags(apfId, aefId, apiId, apiVersion, resourceName)
108 log.Debugf("createKongRoute, tags %s", tags)
110 serviceName := apiId + "_" + resourceName
111 routeName := serviceName
113 log.Debugf("createKongRoute, serviceName %s", serviceName)
114 log.Debugf("createKongRoute, routeName %s", routeName)
115 log.Debugf("createKongRoute, aefId %s", aefId)
117 uri := buildUriWithVersion(apiVersion, resource.Uri)
118 log.Debugf("createKongRoute, uri %s", uri)
120 statusCode, err := sd.createKongService(kongControlPlaneURL, serviceName, uri, tags)
121 if (err != nil) || (statusCode != http.StatusCreated) {
122 return statusCode, err
125 kongRoutesURL := kongControlPlaneURL + "/services/" + serviceName + "/routes"
127 // Define the route information for Kong
128 kongRouteInfo := map[string]interface{}{
130 "paths": []string{uri},
131 "methods": resource.Operations,
136 // Make the POST request to create the Kong service
137 resp, err := client.R().
138 SetHeader("Content-Type", "application/json").
139 SetBody(kongRouteInfo).
142 // Check for errors in the request
144 log.Debugf("createKongRoute POST Error: %v", err)
145 return resp.StatusCode(), err
148 // Check the response status code
149 if resp.StatusCode() == http.StatusCreated {
150 log.Infof("kong route %s created successfully", routeName)
152 log.Debugf("kongRoutesURL %s", kongRoutesURL)
153 err = fmt.Errorf("error creating Kong route. Status code: %d", resp.StatusCode())
154 log.Error(err.Error())
155 log.Errorf("response body: %s", resp.Body())
156 return resp.StatusCode(), err
159 return resp.StatusCode(), nil
162 func buildUriWithVersion(apiVersion string, uri string) string {
163 if apiVersion != "" {
164 if apiVersion[0] != '/' {
165 apiVersion = "/" + apiVersion
167 if apiVersion[len(apiVersion)-1] != '/' && uri[0] != '/' {
168 apiVersion = apiVersion + "/"
170 uri = apiVersion + uri
175 func buildTags(apfId string, aefId string, apiId string, apiVersion string, resourceName string) []string {
176 tagsMap := map[string]string{
180 "apiVersion": apiVersion,
181 "resourceName": resourceName,
184 // Convert the map to a slice of strings
185 var tagsSlice []string
186 for key, value := range tagsMap {
187 str := fmt.Sprintf("%s: %s", key, value)
188 tagsSlice = append(tagsSlice, str)
194 func (sd *ServiceAPIDescription) createKongService(kongControlPlaneURL string, kongServiceName string, kongServiceUri string, tags []string) (int, error) {
195 log.Tracef("entering createKongService")
196 log.Tracef("createKongService, kongServiceName %s", kongServiceName)
198 // Define the service information for Kong
199 firstAEFProfileIpv4Addr, firstAEFProfilePort, err := sd.findFirstAEFProfile()
201 return http.StatusBadRequest, err
204 kongControlPlaneURLParsed, err := url.Parse(kongControlPlaneURL)
206 return http.StatusInternalServerError, err
208 log.Debugf("kongControlPlaneURL %s", kongControlPlaneURL)
209 log.Debugf("kongControlPlaneURLParsed.Scheme %s", kongControlPlaneURLParsed.Scheme)
211 kongServiceInfo := map[string]interface{}{
212 "host": firstAEFProfileIpv4Addr,
213 "name": kongServiceName,
214 "port": firstAEFProfilePort,
215 "protocol": kongControlPlaneURLParsed.Scheme,
216 "path": kongServiceUri,
220 // Kong admin API endpoint for creating a service
221 kongServicesURL := kongControlPlaneURL + "/services"
223 // Create a new Resty client
224 client := resty.New()
226 // Make the POST request to create the Kong service
227 resp, err := client.R().
228 SetHeader("Content-Type", "application/json").
229 SetBody(kongServiceInfo).
230 Post(kongServicesURL)
232 // Check for errors in the request
234 log.Errorf("create Kong Service Request Error: %v", err)
235 return http.StatusInternalServerError, err
238 // Check the response status code
239 statusCode := resp.StatusCode()
240 if statusCode == http.StatusCreated {
241 log.Infof("kong service %s created successfully", kongServiceName)
242 } else if resp.StatusCode() == http.StatusConflict {
243 log.Errorf("kong service already exists. Status code: %d", resp.StatusCode())
244 err = fmt.Errorf("service with identical apiName is already published") // for compatibilty with Capif error message on a duplicate service
245 statusCode = http.StatusForbidden // for compatibilty with the spec, TS29222_CAPIF_Publish_Service_API
247 err = fmt.Errorf("error creating Kong service. Status code: %d", resp.StatusCode())
250 log.Errorf(err.Error())
251 log.Errorf("response body: %s", resp.Body())
254 return statusCode, err
257 func (sd *ServiceAPIDescription) findFirstAEFProfile() (common29122.Ipv4Addr, common29122.Port, error) {
258 log.Tracef("entering findFirstAEFProfile")
259 var aefProfile AefProfile
260 if *sd.AefProfiles != nil {
261 aefProfile = (*sd.AefProfiles)[0]
263 if (*sd.AefProfiles == nil) || (aefProfile.InterfaceDescriptions == nil) {
264 err := errors.New("cannot read interfaceDescription")
265 log.Errorf(err.Error())
266 return "", common29122.Port(0), err
269 interfaceDescription := (*aefProfile.InterfaceDescriptions)[0]
270 firstIpv4Addr := *interfaceDescription.Ipv4Addr
271 firstPort := *interfaceDescription.Port
273 log.Debugf("findFirstAEFProfile firstIpv4Addr %s firstPort %d", firstIpv4Addr, firstPort)
275 return firstIpv4Addr, firstPort, nil
278 // Update our exposures to point to Kong by replacing in incoming interface description with Kong interface descriptions.
279 func (sd *ServiceAPIDescription) updateInterfaceDescription(kongDataPlaneIPv4 common29122.Ipv4Addr, kongDataPlanePort common29122.Port, kongDomain string) {
280 log.Trace("updating InterfaceDescriptions")
281 log.Debugf("InterfaceDescriptions kongDataPlaneIPv4 %s", kongDataPlaneIPv4)
283 interfaceDesc := InterfaceDescription{
284 Ipv4Addr: &kongDataPlaneIPv4,
285 Port: &kongDataPlanePort,
287 interfaceDescs := []InterfaceDescription{interfaceDesc}
289 profiles := *sd.AefProfiles
290 for i, profile := range profiles {
291 profile.DomainName = &kongDomain
292 profile.InterfaceDescriptions = &interfaceDescs
293 profiles[i] = profile
297 func (sd *ServiceAPIDescription) UnregisterKong(kongDomain string, kongProtocol string, kongControlPlaneIPv4 common29122.Ipv4Addr, kongControlPlanePort common29122.Port) (int, error) {
298 log.Trace("entering UnregisterKong")
304 kongControlPlaneURL := fmt.Sprintf("%s://%s:%d", kongProtocol, kongControlPlaneIPv4, kongControlPlanePort)
306 statusCode, err = sd.deleteKongRoutes(kongControlPlaneURL)
307 if (err != nil) || (statusCode != http.StatusNoContent) {
308 return statusCode, err
311 log.Trace("exiting from UnregisterKong")
312 return statusCode, nil
315 func (sd *ServiceAPIDescription) deleteKongRoutes(kongControlPlaneURL string) (int, error) {
316 log.Trace("entering deleteKongRoutes")
323 client := resty.New()
325 profiles := *sd.AefProfiles
326 for _, profile := range profiles {
327 log.Debugf("deleteKongRoutes, AefId %s", profile.AefId)
328 for _, version := range profile.Versions {
329 log.Debugf("deleteKongRoutes, apiVersion \"%s\"", version.ApiVersion)
330 for _, resource := range *version.Resources {
331 statusCode, err = sd.deleteKongRoute(kongControlPlaneURL, client, resource, profile.AefId, version.ApiVersion)
332 if (err != nil) || (statusCode != http.StatusNoContent) {
333 return statusCode, err
338 return statusCode, nil
341 func (sd *ServiceAPIDescription) deleteKongRoute(kongControlPlaneURL string, client *resty.Client, resource Resource, aefId string, apiVersion string) (int, error) {
342 log.Trace("entering deleteKongRoute")
343 routeName := *sd.ApiId + "_" + resource.ResourceName
344 kongRoutesURL := kongControlPlaneURL + "/routes/" + routeName + "?tags=" + aefId
345 log.Debugf("deleteKongRoute, routeName %s, tag %s", routeName, aefId)
347 // Make the DELETE request to delete the Kong route
348 resp, err := client.R().Delete(kongRoutesURL)
350 // Check for errors in the request
352 log.Errorf("error on Kong route delete: %v", err)
353 return resp.StatusCode(), err
356 // Check the response status code
357 if resp.StatusCode() == http.StatusNoContent {
358 log.Infof("kong route %s deleted successfully", routeName)
360 log.Debugf("kongRoutesURL: %s", kongRoutesURL)
361 log.Errorf("error deleting Kong route. Status code: %d", resp.StatusCode())
362 log.Errorf("response body: %s", resp.Body())
363 return resp.StatusCode(), err
366 statusCode, err := sd.deleteKongService(kongControlPlaneURL, routeName, aefId)
367 if (err != nil) || (statusCode != http.StatusNoContent) {
368 return statusCode, err
370 return statusCode, err
373 func (sd *ServiceAPIDescription) deleteKongService(kongControlPlaneURL string, serviceName string, aefId string) (int, error) {
374 log.Trace("entering deleteKongService")
375 // Define the service information for Kong
376 // Kong admin API endpoint for deleting a service
377 kongServicesURL := kongControlPlaneURL + "/services/" + serviceName + "?tags=" + aefId
379 // Create a new Resty client
380 client := resty.New()
382 // Make the DELETE request to delete the Kong service
383 resp, err := client.R().
384 SetHeader("Content-Type", "application/json").
385 Delete(kongServicesURL)
387 // Check for errors in the request
389 log.Errorf("delete kong service request: %v", err)
390 return http.StatusInternalServerError, err
393 // Check the response status code
394 if resp.StatusCode() == http.StatusNoContent {
395 log.Infof("kong service %s deleted successfully", serviceName)
397 log.Debugf("kongServicesURL: %s", kongServicesURL)
398 log.Errorf("deleting Kong service, status code: %d", resp.StatusCode())
399 log.Errorf("response body: %s", resp.Body())
401 return resp.StatusCode(), nil