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, kongProtocol string, kongIPv4 common29122.Ipv4Addr, kongDataPlanePort common29122.Port, kongControlPlanePort common29122.Port) (int, error) {
42 log.Trace("entering RegisterKong")
47 kongControlPlaneURL := fmt.Sprintf("%s://%s:%d", kongProtocol, kongIPv4, kongControlPlanePort)
49 statusCode, err = sd.createKongRoutes(kongControlPlaneURL)
50 if (err != nil) || (statusCode != http.StatusCreated) {
51 return statusCode, err
54 sd.updateInterfaceDescription(kongIPv4, kongDataPlanePort, kongDomain)
56 log.Trace("exiting from RegisterKong")
57 return statusCode, nil
60 func (sd *ServiceAPIDescription) createKongRoutes(kongControlPlaneURL string) (int, error) {
61 log.Trace("entering createKongRoutes")
69 profiles := *sd.AefProfiles
70 for _, profile := range profiles {
71 log.Debugf("createKongRoutes, AefId %s", profile.AefId)
72 for _, version := range profile.Versions {
73 log.Debugf("createKongRoutes, apiVersion \"%s\"", version.ApiVersion)
74 for _, resource := range *version.Resources {
75 statusCode, err = sd.createKongRoute(kongControlPlaneURL, client, resource, profile.AefId, version.ApiVersion)
76 if (err != nil) || (statusCode != http.StatusCreated) {
77 return statusCode, err
82 return statusCode, nil
85 func (sd *ServiceAPIDescription) createKongRoute(kongControlPlaneURL string, client *resty.Client, resource Resource, aefId string, apiVersion string) (int, error) {
86 log.Trace("entering createKongRoute")
90 if apiVersion[0] != '/' {
91 apiVersion = "/" + apiVersion
93 if apiVersion[len(apiVersion)-1] != '/' && resource.Uri[0] != '/' {
94 apiVersion = apiVersion + "/"
96 uri = apiVersion + resource.Uri
99 log.Debugf("createKongRoute, uri %s", uri)
101 serviceName := *sd.ApiId + "_" + resource.ResourceName
102 log.Debugf("createKongRoute, serviceName %s", serviceName)
103 log.Debugf("createKongRoute, aefId %s", aefId)
105 statusCode, err := sd.createKongService(kongControlPlaneURL, serviceName, uri, aefId)
106 if (err != nil) || (statusCode != http.StatusCreated) {
107 return statusCode, err
110 routeName := serviceName
111 kongRoutesURL := kongControlPlaneURL + "/services/" + serviceName + "/routes"
113 // Define the route information for Kong
114 kongRouteInfo := map[string]interface{}{
116 "paths": []string{uri},
117 "methods": resource.Operations,
118 "tags": []string{aefId},
122 // Make the POST request to create the Kong service
123 resp, err := client.R().
124 SetHeader("Content-Type", "application/json").
125 SetBody(kongRouteInfo).
128 // Check for errors in the request
130 log.Debugf("createKongRoute POST Error: %v", err)
131 return resp.StatusCode(), err
134 // Check the response status code
135 if resp.StatusCode() == http.StatusCreated {
136 log.Infof("kong route %s created successfully", routeName)
138 err = fmt.Errorf("the Kong service already exists. Status code: %d", resp.StatusCode())
139 log.Error(err.Error())
140 log.Errorf("response body: %s", resp.Body())
141 return resp.StatusCode(), err
144 return resp.StatusCode(), nil
147 func (sd *ServiceAPIDescription) createKongService(kongControlPlaneURL string, kongServiceName string, kongServiceUri string, aefId string) (int, error) {
148 log.Tracef("entering createKongService")
149 log.Tracef("createKongService, kongServiceName %s", kongServiceName)
151 // Define the service information for Kong
152 firstAEFProfileIpv4Addr, firstAEFProfilePort, err := sd.findFirstAEFProfile()
154 return http.StatusBadRequest, err
157 kongControlPlaneURLParsed, err := url.Parse(kongControlPlaneURL)
159 return http.StatusInternalServerError, err
161 log.Debugf("kongControlPlaneURL %s", kongControlPlaneURL)
162 log.Debugf("kongControlPlaneURLParsed.Scheme %s", kongControlPlaneURLParsed.Scheme)
164 kongServiceInfo := map[string]interface{}{
165 "host": firstAEFProfileIpv4Addr,
166 "name": kongServiceName,
167 "port": firstAEFProfilePort,
168 "protocol": kongControlPlaneURLParsed.Scheme,
169 "path": kongServiceUri,
170 "tags": []string{aefId},
173 // Kong admin API endpoint for creating a service
174 kongServicesURL := kongControlPlaneURL + "/services"
176 // Create a new Resty client
177 client := resty.New()
179 // Make the POST request to create the Kong service
180 resp, err := client.R().
181 SetHeader("Content-Type", "application/json").
182 SetBody(kongServiceInfo).
183 Post(kongServicesURL)
185 // Check for errors in the request
187 log.Errorf("create Kong Service Request Error: %v", err)
188 return http.StatusInternalServerError, err
191 // Check the response status code
192 statusCode := resp.StatusCode()
193 if statusCode == http.StatusCreated {
194 log.Infof("kong service %s created successfully", kongServiceName)
195 } else if resp.StatusCode() == http.StatusConflict {
196 log.Errorf("kong service already exists. Status code: %d", resp.StatusCode())
197 err = fmt.Errorf("service with identical apiName is already published") // for compatibilty with Capif error message on a duplicate service
198 statusCode = http.StatusForbidden // for compatibilty with the spec, TS29222_CAPIF_Publish_Service_API
200 err = fmt.Errorf("error creating Kong service. Status code: %d", resp.StatusCode())
203 log.Errorf(err.Error())
204 log.Errorf("response body: %s", resp.Body())
207 return statusCode, err
210 func (sd *ServiceAPIDescription) findFirstAEFProfile() (common29122.Ipv4Addr, common29122.Port, error) {
211 log.Tracef("entering findFirstAEFProfile")
212 var aefProfile AefProfile
213 if *sd.AefProfiles != nil {
214 aefProfile = (*sd.AefProfiles)[0]
216 if (*sd.AefProfiles == nil) || (aefProfile.InterfaceDescriptions == nil) {
217 err := errors.New("cannot read interfaceDescription")
218 log.Errorf(err.Error())
219 return "", common29122.Port(0), err
222 interfaceDescription := (*aefProfile.InterfaceDescriptions)[0]
223 firstIpv4Addr := *interfaceDescription.Ipv4Addr
224 firstPort := *interfaceDescription.Port
226 log.Debugf("findFirstAEFProfile firstIpv4Addr %s firstPort %d", firstIpv4Addr, firstPort)
228 return firstIpv4Addr, firstPort, nil
231 // Update our exposures to point to Kong by replacing in incoming interface description with Kong interface descriptions.
232 func (sd *ServiceAPIDescription) updateInterfaceDescription(kongIPv4 common29122.Ipv4Addr, kongDataPlanePort common29122.Port, kongDomain string) {
233 log.Trace("updating InterfaceDescriptions")
234 interfaceDesc := InterfaceDescription{
236 Port: &kongDataPlanePort,
238 interfaceDescs := []InterfaceDescription{interfaceDesc}
240 profiles := *sd.AefProfiles
241 for i, profile := range profiles {
242 profile.DomainName = &kongDomain
243 profile.InterfaceDescriptions = &interfaceDescs
244 profiles[i] = profile
248 func (sd *ServiceAPIDescription) UnregisterKong(kongDomain string, kongProtocol string, kongIPv4 common29122.Ipv4Addr, kongDataPlanePort common29122.Port, kongControlPlanePort common29122.Port) (int, error) {
249 log.Trace("entering UnregisterKong")
255 kongControlPlaneURL := fmt.Sprintf("%s://%s:%d", kongProtocol, kongIPv4, kongControlPlanePort)
257 statusCode, err = sd.deleteKongRoutes(kongControlPlaneURL)
258 if (err != nil) || (statusCode != http.StatusNoContent) {
259 return statusCode, err
262 log.Trace("exiting from UnregisterKong")
263 return statusCode, nil
266 func (sd *ServiceAPIDescription) deleteKongRoutes(kongControlPlaneURL string) (int, error) {
267 log.Trace("entering deleteKongRoutes")
274 client := resty.New()
276 profiles := *sd.AefProfiles
277 for _, profile := range profiles {
278 log.Debugf("deleteKongRoutes, AefId %s", profile.AefId)
279 for _, version := range profile.Versions {
280 log.Debugf("deleteKongRoutes, apiVersion \"%s\"", version.ApiVersion)
281 for _, resource := range *version.Resources {
282 statusCode, err = sd.deleteKongRoute(kongControlPlaneURL, client, resource, profile.AefId, version.ApiVersion)
283 if (err != nil) || (statusCode != http.StatusNoContent) {
284 return statusCode, err
289 return statusCode, nil
292 func (sd *ServiceAPIDescription) deleteKongRoute(kongControlPlaneURL string, client *resty.Client, resource Resource, aefId string, apiVersion string) (int, error) {
293 log.Trace("entering deleteKongRoute")
294 routeName := *sd.ApiId + "_" + resource.ResourceName
295 kongRoutesURL := kongControlPlaneURL + "/routes/" + routeName + "?tags=" + aefId
296 log.Debugf("deleteKongRoute, routeName %s, tag %s", routeName, aefId)
298 // Make the DELETE request to delete the Kong route
299 resp, err := client.R().Delete(kongRoutesURL)
301 // Check for errors in the request
303 log.Errorf("error on Kong route delete: %v", err)
304 return resp.StatusCode(), err
307 // Check the response status code
308 if resp.StatusCode() == http.StatusNoContent {
309 log.Infof("kong route %s deleted successfully", routeName)
311 log.Errorf("error deleting Kong route. Status code: %d", resp.StatusCode())
312 log.Errorf("response body: %s", resp.Body())
313 return resp.StatusCode(), err
316 statusCode, err := sd.deleteKongService(kongControlPlaneURL, routeName, aefId)
317 if (err != nil) || (statusCode != http.StatusNoContent) {
318 return statusCode, err
320 return statusCode, err
323 func (sd *ServiceAPIDescription) deleteKongService(kongControlPlaneURL string, serviceName string, aefId string) (int, error) {
324 log.Trace("entering deleteKongService")
325 // Define the service information for Kong
326 // Kong admin API endpoint for deleting a service
327 kongServicesURL := kongControlPlaneURL + "/services/" + serviceName + "?tags=" + aefId
329 // Create a new Resty client
330 client := resty.New()
332 // Make the DELETE request to delete the Kong service
333 resp, err := client.R().
334 SetHeader("Content-Type", "application/json").
335 Delete(kongServicesURL)
337 // Check for errors in the request
339 log.Errorf("delete kong service request: %v", err)
340 return http.StatusInternalServerError, err
343 // Check the response status code
344 if resp.StatusCode() == http.StatusNoContent {
345 log.Infof("kong service %s deleted successfully", serviceName)
347 log.Errorf("deleting Kong service, status code: %d", resp.StatusCode())
348 log.Errorf("response body: %s", resp.Body())
350 return resp.StatusCode(), nil