NONRTRIC-1005: Add multiple Interface descriptions 47/13247/9
authorDenisGNoonan <denis.noonan@est.tech>
Tue, 6 Aug 2024 15:50:42 +0000 (16:50 +0100)
committerDenisGNoonan <denis.noonan@est.tech>
Thu, 15 Aug 2024 09:49:37 +0000 (10:49 +0100)
Change-Id: I2a0e208a05d04cb4acc5bc50932925a80cecf3f5
Signed-off-by: DenisGNoonan <denis.noonan@est.tech>
docs/release-notes.rst
servicemanager/internal/discoverservice/discoverservice_test.go
servicemanager/internal/publishservice/publishservice.go
servicemanager/internal/publishservice/publishservice_test.go
servicemanager/internal/publishserviceapi/typeupdate.go
servicemanager/mockkong/kong_mock.go

index 3e47269..b8b51d0 100644 (file)
@@ -17,10 +17,15 @@ Version history SME Service Manager
 | **Date**   | **Ver.** | **Author**       | **Comment**                          |
 |            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
-| 2024-06-26 | 0.1.2    |                  | J Release (Service Manager)          |
+| 2024-06-26 | 0.1.2    |  Denis G Noonan  | J Release (Service Manager)          |
 |            |          |                  | Initial version of Service Manager   |
 |            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
+| TBA        | TBA      |  Denis G Noonan  | Service Manager includes support for |
+|            |          |                  | dynamic URIs, multiple interface     |
+|            |          |                  | descriptions, and improved schema    |
+|            |          |                  | validation.                          |
++------------+----------+------------------+--------------------------------------+
 
 Version history SME CAPIFCore
 =============================
index ab6ae85..3b6fa81 100644 (file)
@@ -731,16 +731,14 @@ func registerHandlers(e *echo.Echo, myEnv map[string]string, myPorts map[string]
 func getServiceAPIDescription(aefId string, apiName string, apiCategory string, apiVersion string, protocol *publishapi.Protocol, dataFormat *publishapi.DataFormat, description string, testServiceIpv4 common29122.Ipv4Addr, testServicePort common29122.Port, commType publishapi.CommunicationType) publishapi.ServiceAPIDescription {
        domainName := "Kong"
        otherDomainName := "otherDomain"
-
        var otherProtocol publishapi.Protocol = "HTTP_2"
+       var DataFormatOther publishapi.DataFormat = "OTHER"
 
        categoryPointer := &apiCategory
        if apiCategory == "" {
                categoryPointer = nil
        }
 
-       var DataFormatOther publishapi.DataFormat = "OTHER"
-
        return publishapi.ServiceAPIDescription{
                AefProfiles: &[]publishapi.AefProfile{
                        {
@@ -775,6 +773,15 @@ func getServiceAPIDescription(aefId string, apiName string, apiCategory string,
                        },
                        {
                                AefId:      aefId, // "otherAefId"
+                               InterfaceDescriptions: &[]publishapi.InterfaceDescription{
+                                       {
+                                               Ipv4Addr: &testServiceIpv4,
+                                               Port:     &testServicePort,
+                                               SecurityMethods: &[]publishapi.SecurityMethod{
+                                                       "PSK",
+                                               },
+                                       },
+                               },
                                DomainName: &otherDomainName,
                                Protocol:   &otherProtocol,
                                DataFormat: &DataFormatOther,
index 4031827..d042cd0 100644 (file)
@@ -103,10 +103,18 @@ func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) e
                        ps.KongDataPlaneIPv4,
                        ps.KongDataPlanePort,
                        apfId)
-       if (err != nil) || (statusCode != http.StatusCreated) {
-               // We can return with http.StatusForbidden if there is a http.StatusConflict detected by Kong
+
+       log.Trace("After RegisterKong")
+
+       if err != nil {
                msg := err.Error()
-               log.Errorf("error on RegisterKong %s", msg)
+               log.Errorf("PostApfIdServiceApis, error on RegisterKong %s", msg)
+               return sendCoreError(ctx, statusCode, msg)
+       }
+       if  statusCode != http.StatusCreated {
+               // We can return with http.StatusForbidden if there is a http.StatusConflict detected by Kong
+               msg := "error detected by Kong"
+               log.Errorf(msg)
                return sendCoreError(ctx, statusCode, msg)
        }
 
index 31d5293..eb33c38 100644 (file)
@@ -352,7 +352,7 @@ func TestPublishUnpublishServiceMissingInterface(t *testing.T) {
        err = result.UnmarshalJsonToObject(&resultError)
        assert.NoError(t, err, "error unmarshaling response")
 
-       assert.Contains(t, *resultError.Cause, "cannot read interfaceDescription")
+       assert.Contains(t, *resultError.Cause, "cannot read InterfaceDescriptions")
 }
 
 
@@ -434,7 +434,7 @@ func TestPublishUnpublishWithoutVersionId(t *testing.T) {
        var operation publishapi.Operation = "GET"
        assert.Equal(t, operation, (*resource.Operations)[0])
        assert.Equal(t, "helloworld", resource.ResourceName)
-       assert.Equal(t, "/helloworld-v1/helloworld", resource.Uri)
+       assert.Equal(t, "/helloworld-v1/port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/helloworld", resource.Uri)
 }
 
 func TestPublishUnpublishVersionId(t *testing.T) {
@@ -516,7 +516,7 @@ func TestPublishUnpublishVersionId(t *testing.T) {
        assert.Equal(t, operation, (*resource.Operations)[0])
 
        assert.Equal(t, "helloworld-id", resource.ResourceName)
-       assert.Equal(t, "~/helloworld-v1-id/helloworld/v1/(?<helloworld-id>[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*)", resource.Uri)
+       assert.Equal(t, "~/helloworld-v1-id/port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/helloworld/v1/(?<helloworld-id>[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*)", resource.Uri)
 }
 
 func TestPublishUnpublishServiceNoVersionWithId(t *testing.T) {
@@ -598,7 +598,7 @@ func TestPublishUnpublishServiceNoVersionWithId(t *testing.T) {
        assert.Equal(t, operation, (*resource.Operations)[0])
 
        assert.Equal(t, "helloworld-no-version", resource.ResourceName)
-       assert.Equal(t, "~/helloworld-no-version/helloworld/(?<helloworld-id>[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*)", resource.Uri)
+       assert.Equal(t, "~/helloworld-no-version/port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/helloworld/(?<helloworld-id>[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*)", resource.Uri)
 
        capifCleanUp()
 }
index 5ecc7e5..9480104 100644 (file)
@@ -26,9 +26,11 @@ import (
        "net/http"
        "net/url"
        "regexp"
+       "strconv"
        "strings"
 
        resty "github.com/go-resty/resty/v2"
+       "github.com/google/uuid"
        log "github.com/sirupsen/logrus"
 
        common29122 "oransc.org/nonrtric/servicemanager/internal/common29122"
@@ -57,7 +59,7 @@ func (sd *ServiceAPIDescription) RegisterKong(
        )
        kongControlPlaneURL := fmt.Sprintf("%s://%s:%d", kongProtocol, kongControlPlaneIPv4, kongControlPlanePort)
 
-       statusCode, err = sd.createKongRoutes(kongControlPlaneURL, apfId)
+       statusCode, err = sd.createKongInterfaceDescriptions(kongControlPlaneURL, apfId)
        if (err != nil) || (statusCode != http.StatusCreated) {
                return statusCode, err
        }
@@ -68,107 +70,411 @@ func (sd *ServiceAPIDescription) RegisterKong(
        return statusCode, nil
 }
 
-func (sd *ServiceAPIDescription) createKongRoutes(kongControlPlaneURL string, apfId string) (int, error) {
-       log.Trace("entering createKongRoutes")
+func (sd *ServiceAPIDescription) createKongInterfaceDescriptions(kongControlPlaneURL string, apfId string) (int, error) {
+       log.Trace("entering createKongInterfaceDescriptions")
+
        var (
                statusCode int
                err        error
        )
-
        client := resty.New()
+       outputUris := []string{}
+
+       if sd == nil  {
+               err = errors.New("cannot read ServiceAPIDescription")
+               log.Errorf(err.Error())
+               return http.StatusBadRequest, err
+       }
+
+       if (sd.AefProfiles == nil) || (len(*sd.AefProfiles) < 1) {
+               err = errors.New("cannot read AefProfiles")
+               log.Errorf(err.Error())
+               return http.StatusBadRequest, err
+       }
 
        profiles := *sd.AefProfiles
+       for _, profile := range profiles {
+               log.Debugf("createKongInterfaceDescriptions, AefId %s", profile.AefId)
+
+               if (profile.Versions == nil) || (len(profile.Versions) < 1) {
+                       err := errors.New("cannot read Versions")
+                       log.Errorf(err.Error())
+                       return http.StatusBadRequest, err
+               }
+
+               for _, version := range profile.Versions {
+                       log.Debugf("createKongInterfaceDescriptions, apiVersion \"%s\"", version.ApiVersion)
+
+                       if (profile.InterfaceDescriptions == nil) || (len(*profile.InterfaceDescriptions) < 1) {
+                               err := errors.New("cannot read InterfaceDescriptions")
+                               log.Errorf(err.Error())
+                               return http.StatusBadRequest, err
+                       }
+
+                       for _, interfaceDescription := range *profile.InterfaceDescriptions {
+                               log.Debugf("createKongInterfaceDescriptions, Ipv4Addr %s", *interfaceDescription.Ipv4Addr)
+                               log.Debugf("createKongInterfaceDescriptions, Port %d", *interfaceDescription.Port)
+                               if uint(*interfaceDescription.Port) > 65535 {
+                                       err := errors.New("invalid Port")
+                                       log.Errorf(err.Error())
+                                       return http.StatusBadRequest, err
+                               }
+
+                               if interfaceDescription.SecurityMethods == nil {
+                                       err := errors.New("cannot read SecurityMethods")
+                                       log.Errorf(err.Error())
+                                       return http.StatusBadRequest, err
+                               }
+
+                               for _, securityMethod := range *interfaceDescription.SecurityMethods {
+                                       log.Debugf("createKongInterfaceDescriptions, SecurityMethod %s", securityMethod)
+
+                                       if (securityMethod != SecurityMethodOAUTH) && (securityMethod != SecurityMethodPKI) && (securityMethod != SecurityMethodPSK) {
+                                               msg := fmt.Sprintf("invalid SecurityMethod %s", securityMethod)
+                                               err := errors.New(msg)
+                                               log.Errorf(err.Error())
+                                               return http.StatusBadRequest, err
+                                       }
+                               }
+
+                               if (version.Resources == nil) || (len(*version.Resources) < 1) {
+                                       err := errors.New("cannot read Resources")
+                                       log.Errorf(err.Error())
+                                       return http.StatusBadRequest, err
+                               }
+
+                               for _, resource := range *version.Resources {
+                                       var kongRouteUri string
+                                       kongRouteUri, statusCode, err = sd.createKongServiceRoutePrecheck(kongControlPlaneURL, client, interfaceDescription, resource, apfId, profile.AefId, version.ApiVersion)
+                                       if (err != nil) || (statusCode != http.StatusCreated) {
+                                               return statusCode, err
+                                       }
+                                       log.Debugf("createKongInterfaceDescriptions, kongRouteUri %s", kongRouteUri)
+                                       outputUris = append(outputUris, kongRouteUri)
+                                       log.Tracef("createKongInterfaceDescriptions, len(outputUris) %d", len(outputUris))
+                                       log.Tracef("createKongInterfaceDescriptions, outputUris %v", outputUris)
+                               }
+                       }
+               }
+       }
+
+       // Our list of returned resources has the new resource with the hash code and version number
+       m := 0
        for i, profile := range profiles {
-               log.Debugf("createKongRoutes, AefId %s", profile.AefId)
                for j, version := range profile.Versions {
-                       log.Debugf("createKongRoutes, apiVersion \"%s\"", version.ApiVersion)
-                       for k, resource := range *version.Resources {
-                               statusCode, err = sd.createKongRoute(kongControlPlaneURL, client, &resource, apfId, profile.AefId, version.ApiVersion)
-                               if (err != nil) || (statusCode != http.StatusCreated) {
-                                       return statusCode, err
+                       var newResources []Resource
+                       for range *profile.InterfaceDescriptions {
+                               log.Tracef("createKongInterfaceDescriptions, range over *profile.InterfaceDescriptions")
+                               for _, resource := range *version.Resources {
+                                       log.Tracef("createKongInterfaceDescriptions, m %d outputUris[m] %s", m, outputUris[m])
+                                       resource.Uri = outputUris[m]
+                                       m = m + 1
+                                       // Build a new list of resources with updated uris
+                                       newResources = append(newResources, resource)
+                                       log.Tracef("createKongInterfaceDescriptions, newResources %v", newResources)
                                }
-                               (*profiles[i].Versions[j].Resources)[k] = resource
                        }
+                       // Swap over to the new list of uris
+                       *profiles[i].Versions[j].Resources = newResources
+                       log.Tracef("createKongInterfaceDescriptions, assigned *profiles[i].Versions[j].Resources %v", *profiles[i].Versions[j].Resources)
                }
        }
+       log.Tracef("exiting createKongInterfaceDescriptions statusCode %d", statusCode)
+
        return statusCode, nil
 }
 
-func (sd *ServiceAPIDescription) createKongRoute(
+func (sd *ServiceAPIDescription) createKongServiceRoutePrecheck(
                kongControlPlaneURL string,
                client *resty.Client,
-               resource *Resource,
+               interfaceDescription InterfaceDescription,
+               resource Resource,
                apfId string,
                aefId string,
-               apiVersion string ) (int, error) {
-       log.Trace("entering createKongRoute")
+               apiVersion string ) (string, int, error) {
+       log.Trace("entering createKongServiceRoutePrecheck")
+       log.Debugf("createKongServiceRoutePrecheck, aefId %s", aefId)
 
-       resourceName := resource.ResourceName
-       apiId := *sd.ApiId
+       if (resource.Operations == nil) || (len(*resource.Operations) < 1) {
+               err := errors.New("cannot read Resource.Operations")
+               log.Errorf(err.Error())
+               return "", http.StatusBadRequest, err
+       }
 
-       tags := buildTags(apfId, aefId, apiId, apiVersion, resourceName)
-       log.Debugf("createKongRoute, tags %s", tags)
+       log.Debugf("createKongServiceRoutePrecheck, resource.Uri %s", resource.Uri)
+       if resource.Uri == "" {
+               err := errors.New("cannot read Resource.Uri")
+               log.Errorf(err.Error())
+               return "", http.StatusBadRequest, err
+       }
 
-       serviceName := apiId + "_" + resourceName
-       routeName := serviceName
+       log.Debugf("createKongServiceRoutePrecheck, ResourceName %v", resource.ResourceName)
+
+       if resource.ResourceName == "" {
+               err := errors.New("cannot read Resource.ResourceName")
+               log.Errorf(err.Error())
+               return "", http.StatusBadRequest, err
+       }
 
-       log.Debugf("createKongRoute, serviceName %s", serviceName)
-       log.Debugf("createKongRoute, routeName %s", routeName)
-       log.Debugf("createKongRoute, aefId %s", aefId)
+       if (resource.CommType != CommunicationTypeREQUESTRESPONSE) && (resource.CommType != CommunicationTypeSUBSCRIBENOTIFY) {
+               err := errors.New("invalid Resource.CommType")
+               log.Errorf(err.Error())
+               return "", http.StatusBadRequest, err
+       }
 
        uri := insertVersion(apiVersion, resource.Uri)
-       log.Debugf("createKongRoute, uri %s", uri)
+       log.Debugf("createKongServiceRoutePrecheck, uri %s", uri)
 
-       // Create a url.Values map to hold the form data
-       data := url.Values{}
-       serviceUri := uri
+       kongRouteUri, statusCode, err := sd.createKongServiceRoute(kongControlPlaneURL, client, interfaceDescription, uri, apfId, aefId, apiVersion, resource)
+       if (err != nil) || ((statusCode != http.StatusCreated) ) {
+               // We carry on if we tried to create a duplicate service. We depend on Kong route matching.
+               return kongRouteUri, statusCode, err
+       }
+
+       return kongRouteUri, statusCode, err
+}
+
+func insertVersion(version string, route string) string {
+       versionedRoute := route
+
+       if version != "" {
+               sep := "/"
+               n := 3
+
+               foundRegEx := false
+               if strings.HasPrefix(route, "~") {
+                       log.Debug("insertVersion, found regex prefix")
+                       foundRegEx = true
+                       route = strings.TrimPrefix(route, "~")
+               }
+
+               log.Debugf("insertVersion route %s", route)
+               split := strings.SplitAfterN(route, sep, n)
+               log.Debugf("insertVersion split %q", split)
+
+               versionedRoute = split[0]
+               if len(split) == 2 {
+                       versionedRoute = split[0] + split[1]
+               } else if len(split) > 2 {
+                       versionedRoute = split[0] + split[1] + version + sep + split[2]
+               }
+
+               if foundRegEx {
+                       versionedRoute = "~" + versionedRoute
+               }
+       }
+       log.Debugf("insertVersion versionedRoute %s", versionedRoute)
+
+       return versionedRoute
+}
 
+func (sd *ServiceAPIDescription) createKongServiceRoute(
+               kongControlPlaneURL string,
+               client *resty.Client,
+               interfaceDescription InterfaceDescription,
+               uri string,
+               apfId string,
+               aefId string,
+               apiVersion string,
+               resource Resource) (string, int, error) {
+       log.Tracef("entering createKongServiceRoute")
+
+       var (
+               statusCode int
+               err error
+       )
+
+       kongControlPlaneURLParsed, err := url.Parse(kongControlPlaneURL)
+       if err != nil {
+               return "", http.StatusInternalServerError, err
+       }
+       log.Debugf("createKongServiceRoute, kongControlPlaneURL %s", kongControlPlaneURL)
+       log.Debugf("createKongServiceRoute, kongControlPlaneURLParsed.Scheme %s", kongControlPlaneURLParsed.Scheme)
+
+       kongServiceUri := uri
        foundRegEx := false
        if strings.HasPrefix(uri, "~") {
-               log.Debug("createKongRoute, found regex prefix")
+               log.Debug("createKongServiceRoute, found regex prefix")
                foundRegEx = true
-               data.Set("strip_path", "false")
-               serviceUri = "/"
+
+               // For our Kong Service path, we omit the leading ~ and take the path up to the regex, not including the '('
+               kongServiceUri = uri[1:]
+               index := strings.Index(kongServiceUri, "(?")
+               if (index != -1 ) {
+                       kongServiceUri = kongServiceUri[:index]
+               } else {
+                       log.Errorf("createKongServiceRoute, regex characters '(?' not found in the regex %s", kongServiceUri)
+                       return "", http.StatusBadRequest, err
+               }
        } else {
-               log.Debug("createKongRoute, no regex prefix found")
-               data.Set("strip_path", "true")
+               log.Debug("createKongServiceRoute, no regex prefix found")
        }
 
-       log.Debugf("createKongRoute, serviceUri %s", serviceUri)
-       log.Debugf("createKongRoute, strip_path %s", data.Get("strip_path"))
+       log.Debugf("createKongServiceRoute, kongServiceUri %s", kongServiceUri)
 
-       routeUri := prependUri(sd.ApiName, uri)
-       log.Debugf("createKongRoute, routeUri %s", routeUri)
-       resource.Uri = routeUri
+       ipv4Addr := *interfaceDescription.Ipv4Addr
+       port := *interfaceDescription.Port
 
-       statusCode, err := sd.createKongService(kongControlPlaneURL, serviceName, serviceUri, tags)
-       if (err != nil) || ((statusCode != http.StatusCreated) && (statusCode != http.StatusForbidden)) {
-               // We carry on if we tried to create a duplicate service. We depend on Kong route matching.
-               return statusCode, err
+       portAsInt := int(port)
+       interfaceDescriptionSeed := string(ipv4Addr) + strconv.Itoa(portAsInt)
+       interfaceDescUuid := uuid.NewSHA1(uuid.NameSpaceURL, []byte(interfaceDescriptionSeed))
+       uriPrefix := "port-" + strconv.Itoa(portAsInt) + "-hash-" + interfaceDescUuid.String()
+
+       resourceName := resource.ResourceName
+
+       apiId := *sd.ApiId
+       kongServiceName := apiId + "-" + resourceName
+       kongServiceNamePrefix := kongServiceName + "-" + uriPrefix
+
+       log.Debugf("createKongServiceRoute, kongServiceName %s", kongServiceName)
+       log.Debugf("createKongServiceRoute, kongServiceNamePrefix %s", kongServiceNamePrefix)
+
+       tags := buildTags(apfId, aefId, apiId, apiVersion, resourceName)
+       log.Debugf("createKongServiceRoute, tags %s", tags)
+
+       kongServiceInfo := map[string]interface{}{
+               "host":     ipv4Addr,
+               "name":     kongServiceNamePrefix,
+               "port":     port,
+               "protocol": kongControlPlaneURLParsed.Scheme,
+               "path":     kongServiceUri,
+               "tags":     tags,
+       }
+
+       // Kong admin API endpoint for creating a service
+       kongServicesURL := kongControlPlaneURL + "/services"
+
+       // Make the POST request to create the Kong service
+       resp, err := client.R().
+               SetHeader("Content-Type", "application/json").
+               SetBody(kongServiceInfo).
+               Post(kongServicesURL)
+
+       // Check for errors in the request
+       if err != nil {
+               log.Errorf("createKongServiceRoute, Request Error: %v", err)
+               return "", http.StatusInternalServerError, err
+       }
+
+       // Check the response status code
+       statusCode = resp.StatusCode()
+       if statusCode == http.StatusCreated {
+               log.Infof("kong service %s created successfully", kongServiceNamePrefix)
+       } else if resp.StatusCode() == http.StatusConflict {
+               log.Errorf("kong service already exists. Status code: %d", resp.StatusCode())
+               err = fmt.Errorf("service with identical apiName is already published") // for compatibilty with Capif error message on a duplicate service
+               statusCode = http.StatusForbidden                                       // for compatibilty with the spec, TS29222_CAPIF_Publish_Service_API
+       } else {
+               err = fmt.Errorf("error creating Kong service. Status code: %d", resp.StatusCode())
+       }
+       if err != nil {
+               log.Errorf(err.Error())
+               log.Errorf("response body: %s", resp.Body())
+               return "", statusCode, err
+       }
+
+       // Create matching route
+       routeName := kongServiceNamePrefix
+       kongRouteUri := uri
+
+       kongRouteUri = prependUri(uriPrefix, kongRouteUri)
+       log.Debugf("createKongServiceRoute, kongRouteUri with uriPrefix %s", kongRouteUri)
+
+       kongRouteUri = prependUri(sd.ApiName, kongRouteUri)
+       log.Debugf("createKongServiceRoute, kongRouteUri with apiName %s", kongRouteUri)
+
+       kongRouteUri, statusCode, err = sd.createRouteForService(kongControlPlaneURL, client, resource, routeName, kongRouteUri, uri, tags, foundRegEx)
+       if err != nil {
+               log.Errorf(err.Error())
+               return kongRouteUri, statusCode, err
+       }
+
+       return kongRouteUri, statusCode, err
+}
+
+func buildTags(apfId string, aefId string, apiId string, apiVersion string, resourceName string) []string  {
+       tagsMap := map[string]string{
+               "apfId": apfId,
+               "aefId": aefId,
+               "apiId": apiId,
+               "apiVersion": apiVersion,
+               "resourceName": resourceName,
+       }
+
+       // Convert the map to a slice of strings
+       var tagsSlice []string
+       for key, value := range tagsMap {
+               str := fmt.Sprintf("%s: %s", key, value)
+               tagsSlice = append(tagsSlice, str)
+       }
+
+       return tagsSlice
+}
+
+func prependUri(prependUri string, uri string) string {
+       if prependUri != "" {
+               trimmedUri := uri
+               foundRegEx := false
+               if strings.HasPrefix(uri, "~") {
+                       log.Debug("prependUri, found regex prefix")
+                       foundRegEx = true
+                       trimmedUri = strings.TrimPrefix(uri, "~")
+                       log.Debugf("prependUri, TrimPrefix trimmedUri %s", trimmedUri)
+               }
+
+               if prependUri[0] != '/' {
+                       prependUri = "/" + prependUri
+               }
+               if prependUri[len(prependUri)-1] != '/' && trimmedUri[0] != '/' {
+                       prependUri = prependUri + "/"
+               }
+               uri = prependUri + trimmedUri
+               if foundRegEx {
+                       uri = "~" + uri
+               }
        }
+       return uri
+}
+
+func (sd *ServiceAPIDescription) createRouteForService(
+               kongControlPlaneURL string,
+               client *resty.Client,
+               resource Resource,
+               routeName string,
+               kongRouteUri string,
+               uri string,
+               tags []string,
+               foundRegEx bool) (string, int, error)  {
+
+       log.Debugf("createRouteForService, kongRouteUri %s", kongRouteUri)
 
+       // Create a url.Values map to hold the form data
+       data := url.Values{}
+       data.Set("strip_path", "true")
+       log.Debugf("createRouteForService, strip_path %s", data.Get("strip_path"))
        data.Set("name", routeName)
 
-       routeUriPaths := []string{routeUri}
+       routeUriPaths := []string{kongRouteUri}
        for _, path := range routeUriPaths {
-               log.Debugf("createKongRoute, path %s", path)
+               log.Debugf("createRouteForService, path %s", path)
                data.Add("paths", path)
-    }
+       }
 
        for _, tag := range tags {
-               log.Debugf("createKongRoute, tag %s", tag)
+               log.Debugf("createRouteForService, tag %s", tag)
                data.Add("tags", tag)
-    }
+       }
 
        for _, op := range *resource.Operations {
-               log.Debugf("createKongRoute, op %s", string(op))
+               log.Debugf("createRouteForService, op %s", string(op))
                data.Add("methods", string(op))
-    }
+       }
 
        // Encode the data to application/x-www-form-urlencoded format
        encodedData := data.Encode()
 
        // Make the POST request to create the Kong service
+       serviceName := routeName
        kongRoutesURL := kongControlPlaneURL + "/services/" + serviceName + "/routes"
        resp, err := client.R().
                SetHeader("Content-Type", "application/x-www-form-urlencoded").
@@ -177,8 +483,8 @@ func (sd *ServiceAPIDescription) createKongRoute(
 
        // Check for errors in the request
        if err != nil {
-               log.Debugf("createKongRoute POST Error: %v", err)
-               return resp.StatusCode(), err
+               log.Debugf("createRouteForService POST Error: %v", err)
+               return kongRouteUri, resp.StatusCode(), err
        }
 
        // Check the response status code
@@ -187,7 +493,7 @@ func (sd *ServiceAPIDescription) createKongRoute(
                if (foundRegEx) {
                        statusCode, err := sd.createRequestTransformer(kongControlPlaneURL, client, routeName, uri)
                        if (err != nil) || ((statusCode != http.StatusCreated) && (statusCode != http.StatusForbidden)) {
-                               return statusCode, err
+                               return kongRouteUri, statusCode, err
                        }
                }
        } else {
@@ -195,10 +501,10 @@ func (sd *ServiceAPIDescription) createKongRoute(
                err = fmt.Errorf("error creating Kong route. Status code: %d", resp.StatusCode())
                log.Error(err.Error())
                log.Errorf("response body: %s", resp.Body())
-               return resp.StatusCode(), err
+               return kongRouteUri, resp.StatusCode(), err
        }
 
-       return resp.StatusCode(), nil
+       return kongRouteUri, resp.StatusCode(), nil
 }
 
 func (sd *ServiceAPIDescription) createRequestTransformer(
@@ -235,7 +541,7 @@ func (sd *ServiceAPIDescription) createRequestTransformer(
 
        // Check the response status code
        if resp.StatusCode() == http.StatusCreated {
-               log.Infof("kong request transformer created successfully for route %s", routeName)
+               log.Infof("kong request transformer for route %s created successfully", routeName)
        } else {
                log.Debugf("kongRequestTransformerURL %s", kongRequestTransformerURL)
                err = fmt.Errorf("error creating Kong request transformer. Status code: %d", resp.StatusCode())
@@ -286,168 +592,6 @@ func deriveTransformPattern(routePattern string) (string, error) {
        return transformPattern, nil
 }
 
-func insertVersion(version string, route string) string {
-       versionedRoute := route
-
-       if version != "" {
-               sep := "/"
-               n := 3
-
-               foundRegEx := false
-               if strings.HasPrefix(route, "~") {
-                       log.Debug("insertVersion, found regex prefix")
-                       foundRegEx = true
-                       route = strings.TrimPrefix(route, "~")
-               }
-
-               log.Debugf("insertVersion route %s", route)
-               split := strings.SplitAfterN(route, sep, n)
-               log.Debugf("insertVersion split %q", split)
-
-               versionedRoute = split[0]
-               if len(split) == 2 {
-                       versionedRoute = split[0] + split[1]
-               } else if len(split) > 2 {
-                       versionedRoute = split[0] + split[1] + version + sep + split[2]
-               }
-
-               if foundRegEx {
-                       versionedRoute = "~" + versionedRoute
-               }
-       }
-       log.Debugf("insertVersion versionedRoute %s", versionedRoute)
-
-       return versionedRoute
-}
-
-func prependUri(prependUri string, uri string) string {
-       if prependUri != "" {
-               trimmedUri := uri
-               foundRegEx := false
-               if strings.HasPrefix(uri, "~") {
-                       log.Debug("prependUri, found regex prefix")
-                       foundRegEx = true
-                       trimmedUri = strings.TrimPrefix(uri, "~")
-                       log.Debugf("prependUri, TrimPrefix trimmedUri %s", trimmedUri)
-               }
-
-               if prependUri[0] != '/' {
-                       prependUri = "/" + prependUri
-               }
-               if prependUri[len(prependUri)-1] != '/' && trimmedUri[0] != '/' {
-                       prependUri = prependUri + "/"
-               }
-               uri = prependUri + trimmedUri
-               if foundRegEx {
-                       uri = "~" + uri
-               }
-       }
-       return uri
-}
-
-func buildTags(apfId string, aefId string, apiId string, apiVersion string, resourceName string) []string  {
-       tagsMap := map[string]string{
-               "apfId": apfId,
-               "aefId": aefId,
-               "apiId": apiId,
-               "apiVersion": apiVersion,
-               "resourceName": resourceName,
-       }
-
-       // Convert the map to a slice of strings
-       var tagsSlice []string
-       for key, value := range tagsMap {
-               str := fmt.Sprintf("%s: %s", key, value)
-               tagsSlice = append(tagsSlice, str)
-       }
-
-       return tagsSlice
-}
-
-func (sd *ServiceAPIDescription) createKongService(kongControlPlaneURL string, kongServiceName string, kongServiceUri string, tags []string) (int, error) {
-       log.Tracef("entering createKongService")
-       log.Tracef("createKongService, kongServiceName %s", kongServiceName)
-
-       // Define the service information for Kong
-       firstAEFProfileIpv4Addr, firstAEFProfilePort, err := sd.findFirstAEFProfile()
-       if err != nil {
-               return http.StatusBadRequest, err
-       }
-
-       kongControlPlaneURLParsed, err := url.Parse(kongControlPlaneURL)
-       if err != nil {
-               return http.StatusInternalServerError, err
-       }
-       log.Debugf("kongControlPlaneURL %s", kongControlPlaneURL)
-       log.Debugf("kongControlPlaneURLParsed.Scheme %s", kongControlPlaneURLParsed.Scheme)
-
-       kongServiceInfo := map[string]interface{}{
-               "host":     firstAEFProfileIpv4Addr,
-               "name":     kongServiceName,
-               "port":     firstAEFProfilePort,
-               "protocol": kongControlPlaneURLParsed.Scheme,
-               "path":     kongServiceUri,
-               "tags":     tags,
-       }
-
-       // Kong admin API endpoint for creating a service
-       kongServicesURL := kongControlPlaneURL + "/services"
-
-       // Create a new Resty client
-       client := resty.New()
-
-       // Make the POST request to create the Kong service
-       resp, err := client.R().
-               SetHeader("Content-Type", "application/json").
-               SetBody(kongServiceInfo).
-               Post(kongServicesURL)
-
-       // Check for errors in the request
-       if err != nil {
-               log.Errorf("create Kong Service Request Error: %v", err)
-               return http.StatusInternalServerError, err
-       }
-
-       // Check the response status code
-       statusCode := resp.StatusCode()
-       if statusCode == http.StatusCreated {
-               log.Infof("kong service %s created successfully", kongServiceName)
-       } else if resp.StatusCode() == http.StatusConflict {
-               log.Errorf("kong service already exists. Status code: %d", resp.StatusCode())
-               err = fmt.Errorf("service with identical apiName is already published") // for compatibilty with Capif error message on a duplicate service
-               statusCode = http.StatusForbidden                                       // for compatibilty with the spec, TS29222_CAPIF_Publish_Service_API
-       } else {
-               err = fmt.Errorf("error creating Kong service. Status code: %d", resp.StatusCode())
-       }
-       if err != nil {
-               log.Errorf(err.Error())
-               log.Errorf("response body: %s", resp.Body())
-       }
-
-       return statusCode, err
-}
-
-func (sd *ServiceAPIDescription) findFirstAEFProfile() (common29122.Ipv4Addr, common29122.Port, error) {
-       log.Tracef("entering findFirstAEFProfile")
-       var aefProfile AefProfile
-       if *sd.AefProfiles != nil {
-               aefProfile = (*sd.AefProfiles)[0]
-       }
-       if (*sd.AefProfiles == nil) || (aefProfile.InterfaceDescriptions == nil) {
-               err := errors.New("cannot read interfaceDescription")
-               log.Errorf(err.Error())
-               return "", common29122.Port(0), err
-       }
-
-       interfaceDescription := (*aefProfile.InterfaceDescriptions)[0]
-       firstIpv4Addr := *interfaceDescription.Ipv4Addr
-       firstPort := *interfaceDescription.Port
-
-       log.Debugf("findFirstAEFProfile firstIpv4Addr %s firstPort %d", firstIpv4Addr, firstPort)
-
-       return firstIpv4Addr, firstPort, nil
-}
-
 // Update our exposures to point to Kong by replacing in incoming interface description with Kong interface descriptions.
 func (sd *ServiceAPIDescription) updateInterfaceDescription(kongDataPlaneIPv4 common29122.Ipv4Addr, kongDataPlanePort common29122.Port, kongDomain string) {
        log.Trace("updating InterfaceDescriptions")
index c0a6dba..8191c03 100644 (file)
@@ -85,6 +85,166 @@ func RegisterHandlers(e *echo.Echo) {
                return c.String(http.StatusCreated, string(body))
        })
 
+       e.POST("/services/api_id_apiName_helloworld-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-v1-id_helloworld-id-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-v1_helloworld-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/routes/api_id_helloworld-v1-id_helloworld-id-04478a3a-d0ef-5a05-a575-db5ee2e33403/plugins", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-no-version_helloworld-no-version-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/routes/api_id_helloworld-no-version_helloworld-no-version-04478a3a-d0ef-5a05-a575-db5ee2e33403/plugins", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName1_helloworld-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName2_helloworld-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName1_app-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName2_app-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-v1-helloworld-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-v1-id-helloworld-id-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/routes/api_id_helloworld-v1-id-helloworld-id-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/plugins", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-no-version-helloworld-no-version-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/routes/api_id_helloworld-no-version-helloworld-no-version-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/plugins", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName-helloworld-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName1-helloworld-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName1-app-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName2-helloworld-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName2-app-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
        e.POST("/services/api_id_apiName_helloworld-id/routes", func(c echo.Context) error {
                body, err := io.ReadAll(c.Request().Body)
                if err != nil {
@@ -245,4 +405,4 @@ func RegisterHandlers(e *echo.Echo) {
                return c.NoContent(http.StatusNoContent)
        })
 
-}
\ No newline at end of file
+}