NONRTRIC-944: ServiceManager - Add kongclearup to Docker image 23/12623/5
authorDenisGNoonan <denis.noonan@est.tech>
Thu, 21 Mar 2024 09:29:57 +0000 (09:29 +0000)
committerDenisGNoonan <denis.noonan@est.tech>
Fri, 29 Mar 2024 11:59:21 +0000 (11:59 +0000)
Issue-ID: NONRTRIC-944
Change-Id: I9c959d6930070acde22d59d3df3248ba125f8f68
Signed-off-by: DenisGNoonan <denis.noonan@est.tech>
.gitignore
servicemanager/Dockerfile
servicemanager/README.md
servicemanager/internal/kongclear/kongclear.go
servicemanager/internal/publishservice/publishservice.go
servicemanager/internal/publishserviceapi/typeupdate.go

index 0dc4f08..8d0a84f 100644 (file)
@@ -7,8 +7,9 @@ docs/_build/
 .pydevproject
 go.work*
 servicemanager.tar.gz
-servicemanager/servicemanager
 servicemanager/specs
+servicemanager/servicemanager
+servicemanager/internal/kongclearup
 coverage.txt
 # Ignore all .env files
 .env*
@@ -16,4 +17,3 @@ coverage.txt
 !.env.example
 # Ignore all files in folders named "mocks"
 **/mocks/*
-servicemanager/specs/
index ddb23ed..85ef603 100644 (file)
@@ -29,6 +29,9 @@ RUN go mod download
 COPY . .
 RUN go build -o /servicemanager
 
+WORKDIR "/app/internal"
+RUN go build kongclearup.go
+
 ##
 ## Deploy
 ##
@@ -41,5 +44,6 @@ WORKDIR /app
 
 ## Copy from "build" stage
 COPY --from=build /servicemanager .
+COPY --from=build /app/internal/kongclearup .
 
 ENTRYPOINT ["/app/servicemanager"]
index a43d9de..4db3c8d 100644 (file)
@@ -102,6 +102,21 @@ The application can also be built as a Docker image, by using the following comm
 docker image build . -t servicemanager
 ```
 
+## Kongclearup
+
+Please note that a special executable has been provided for deleting Kong routes and services that have been created ServiceManager in Kong. This executable is called `kongclearup` and is found in the working directory of the ServiceManger Docker image, at `/app/kongclearup`. When we create a Kong route or service, we add Kong tags with information as follows.
+  * apfId
+  * aefId
+  * apiId
+  * apiVersion
+  * resourceName
+
+When we delete Kong routes and services using `kongclearup`, we check for the existance of these tags, specifically, apfId, apiId and aefId. Only if these tags exist and have values do we proceed to delete the Kong service or route.
+
+The executable `kongclearup` uses the volume-mounted .env file to load the configuration giving the location of Kong.
+
+Please refer to `sme/servicemanager/internal/kongclearup.go`.
+
 ## Stand-alone Deployment on Kubernetes
 
 For a stand-alone deployment, please see the `deploy` folder for configurations to deploy to R1-SME-Manager to Kubernetes. We need the following steps.
@@ -119,7 +134,7 @@ We consolidate the above steps into the script `deploy-to-k8s.sh`. To delete the
       - src/
       - manifests/
 
-We store the Kubernetes manifests files in the manifests in the subfolder. We store the shell scripts in the src folder. 
+We store the Kubernetes manifests files in the manifests in the subfolder. We store the shell scripts in the src folder.
 
 In `deploy-to-k8s.sh`, we copy .env.example and use sed to replace the template values with values for testing/production. You will need to update this part of the script with your own values. There is an example sed replacement in function `substitute_manifest()` in `deploy-to-k8s.sh`. Here, you can substitute your own Docker images for Capifcore and Service Manager for local development.
 
index 068cd7d..6ec3f08 100644 (file)
-// -\r
-//   ========================LICENSE_START=================================\r
-//   O-RAN-SC\r
-//   %%\r
-//   Copyright (C) 2024: OpenInfra Foundation Europe\r
-//   %%\r
-//   Licensed under the Apache License, Version 2.0 (the "License");\r
-//   you may not use this file except in compliance with the License.\r
-//   You may obtain a copy of the License at\r
-//\r
-//        http://www.apache.org/licenses/LICENSE-2.0\r
-//\r
-//   Unless required by applicable law or agreed to in writing, software\r
-//   distributed under the License is distributed on an "AS IS" BASIS,\r
-//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
-//   See the License for the specific language governing permissions and\r
-//   limitations under the License.\r
-//   ========================LICENSE_END===================================\r
-//\r
-\r
-package kongclear\r
-\r
-import (\r
-       "encoding/json"\r
-       "fmt"\r
-       "net/http"\r
-\r
-       log "github.com/sirupsen/logrus"\r
-       resty "github.com/go-resty/resty/v2"\r
-)\r
-\r
-// Service represents the structure for Kong service creation\r
-type KongService struct {\r
-       ID   string `json:"id"`\r
-       Name string `json:"name"`\r
-       URL  string `json:"url"`\r
-}\r
-\r
-type KongServiceResponse struct {\r
-       ID string `json:"id"`\r
-}\r
-\r
-type ServiceResponse struct {\r
-       Data []KongService `json:"data"`\r
-}\r
-\r
-type KongRoute struct {\r
-       ID        string   `json:"id"`\r
-       Name      string   `json:"name"`\r
-       Paths     []string `json:"paths"`\r
-       Service   Service  `json:"service"`\r
-       StripPath bool     `json:"strip_path"`\r
-}\r
-\r
-type RouteResponse struct {\r
-       Data []KongRoute `json:"data"`\r
-}\r
-\r
-type Service struct {\r
-       ID string `json:"id"`\r
-}\r
-\r
-func KongClear(myEnv map[string]string, myPorts map[string]int) error {\r
-       log.Info("attempting to delete all Kong routes and services")\r
-\r
-       kongAdminApiUrl := fmt.Sprintf("%s://%s:%d/", myEnv["KONG_PROTOCOL"], myEnv["KONG_IPV4"], myPorts["KONG_CONTROL_PLANE_PORT"])\r
-\r
-       err := deleteAllRoutes(kongAdminApiUrl)\r
-       if err != nil {\r
-               log.Fatalf("error deleting routes %v", err)\r
-               return err\r
-       }\r
-\r
-       err = deleteAllServices(kongAdminApiUrl)\r
-       if err != nil {\r
-               log.Fatalf("error deleting services %v", err)\r
-               return err\r
-       }\r
-\r
-       log.Info("all Kong routes and services deleted successfully")\r
-       return err\r
-}\r
-\r
-\r
-func deleteAllRoutes(kongAdminApiUrl string) error {\r
-       routes, err := listRoutes(kongAdminApiUrl)\r
-       if err != nil {\r
-               return err\r
-       }\r
-\r
-       for _, route := range routes {\r
-               if err := deleteRoute(kongAdminApiUrl, route.ID); err != nil {\r
-                       return err\r
-               }\r
-       }\r
-\r
-       return nil\r
-}\r
-\r
-func deleteAllServices(kongAdminApiUrl string) error {\r
-       services, err := listServices(kongAdminApiUrl)\r
-       if err != nil {\r
-               return err\r
-       }\r
-\r
-       for _, service := range services {\r
-               if err := deleteService(kongAdminApiUrl, service.ID); err != nil {\r
-                       return err\r
-               }\r
-       }\r
-\r
-       return nil\r
-}\r
-\r
-func listServices(kongAdminApiUrl string) ([]KongService, error) {\r
-       client := resty.New()\r
-       resp, err := client.R().Get(kongAdminApiUrl + "services")\r
-\r
-       if err != nil {\r
-               return nil, err\r
-       }\r
-\r
-       if resp.StatusCode() != http.StatusOK {\r
-               err := fmt.Errorf("failed to list services, status code %d", resp.StatusCode())\r
-               return nil, err\r
-       }\r
-\r
-       var serviceResponse ServiceResponse\r
-       err = json.Unmarshal(resp.Body(), &serviceResponse)\r
-       if err != nil {\r
-               return nil, err\r
-       }\r
-\r
-       log.Infof("kong services %v", serviceResponse.Data)\r
-       return serviceResponse.Data, nil\r
-}\r
-\r
-func listRoutes(kongAdminApiUrl string) ([]KongRoute, error) {\r
-       client := resty.New()\r
-       resp, err := client.R().\r
-               Get(kongAdminApiUrl + "routes")\r
-\r
-       if err != nil {\r
-               return nil, err\r
-       }\r
-\r
-       if resp.StatusCode() != http.StatusOK {\r
-               err := fmt.Errorf("failed to list routes, status code %d", resp.StatusCode())\r
-               return nil, err\r
-       }\r
-\r
-       var routeResponse RouteResponse\r
-       err = json.Unmarshal(resp.Body(), &routeResponse)\r
-       if err != nil {\r
-               return nil, err\r
-       }\r
-\r
-       log.Infof("kong routes %v", routeResponse.Data)\r
-       return routeResponse.Data, nil\r
-}\r
-\r
-func deleteService(kongAdminApiUrl string, serviceID string) error {\r
-       log.Tracef("entering deleteService service ID %v", serviceID)\r
-       client := resty.New()\r
-       resp, err := client.R().Delete(kongAdminApiUrl + "services/" + serviceID)\r
-\r
-       if err != nil {\r
-               return err\r
-       }\r
-\r
-       if resp.StatusCode() != http.StatusNoContent {\r
-               err := fmt.Errorf("failed to delete service %s, status code %d", serviceID, resp.StatusCode())\r
-               return err\r
-       }\r
-\r
-       return nil\r
-}\r
-\r
-func deleteRoute(kongAdminApiUrl string, routeID string) error {\r
-       log.Infof("kong route id %v", routeID)\r
-       client := resty.New()\r
-       resp, err := client.R().Delete(kongAdminApiUrl + "routes/" + routeID)\r
-\r
-       if err != nil {\r
-               return err\r
-       }\r
-\r
-       if resp.StatusCode() != http.StatusNoContent {\r
-               err := fmt.Errorf("failed to delete route %s, status code %d", routeID, resp.StatusCode())\r
-               return err\r
-       }\r
-\r
-       return nil\r
-}
\ No newline at end of file
+// -
+//   ========================LICENSE_START=================================
+//   O-RAN-SC
+//   %%
+//   Copyright (C) 2024: OpenInfra Foundation Europe
+//   %%
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+//   ========================LICENSE_END===================================
+//
+
+package kongclear
+
+import (
+       "encoding/json"
+       "fmt"
+       "net/http"
+       "strings"
+
+       resty "github.com/go-resty/resty/v2"
+       log "github.com/sirupsen/logrus"
+)
+
+// Service represents the structure for Kong service creation
+type KongService struct {
+       ID   string   `json:"id"`
+       Name string   `json:"name"`
+       URL  string   `json:"url"`
+       Tags []string `json:"tags"`
+}
+
+type KongServiceResponse struct {
+       ID string `json:"id"`
+}
+
+type ServiceResponse struct {
+       Data []KongService `json:"data"`
+}
+
+type KongRoute struct {
+       ID        string   `json:"id"`
+       Name      string   `json:"name"`
+       Paths     []string `json:"paths"`
+       Service   Service  `json:"service"`
+       StripPath bool     `json:"strip_path"`
+       Tags      []string `json:"tags"`
+}
+
+type RouteResponse struct {
+       Data []KongRoute `json:"data"`
+}
+
+type Service struct {
+       ID string `json:"id"`
+}
+
+func KongClear(myEnv map[string]string, myPorts map[string]int) error {
+       log.Info("delete only ServiceManager Kong routes and services")
+
+       kongAdminApiUrl := fmt.Sprintf("%s://%s:%d/", myEnv["KONG_PROTOCOL"], myEnv["KONG_IPV4"], myPorts["KONG_CONTROL_PLANE_PORT"])
+
+       err := deleteRoutes(kongAdminApiUrl)
+       if err != nil {
+               log.Fatalf("error deleting routes %v", err)
+               return err
+       }
+
+       err = deleteServices(kongAdminApiUrl)
+       if err != nil {
+               log.Fatalf("error deleting services %v", err)
+               return err
+       }
+
+       log.Info("finished deleting only ServiceManger Kong routes and services")
+       return err
+}
+
+func deleteRoutes(kongAdminApiUrl string) error {
+       routes, err := listRoutes(kongAdminApiUrl)
+       if err != nil {
+               return err
+       }
+
+       for _, route := range routes {
+               if areServiceManagerTags(route.Tags) {
+                       if err := deleteRoute(kongAdminApiUrl, route.ID); err != nil {
+                               return err
+                       }
+               }
+       }
+
+       return nil
+}
+
+func deleteServices(kongAdminApiUrl string) error {
+       services, err := listServices(kongAdminApiUrl)
+       if err != nil {
+               return err
+       }
+
+       for _, service := range services {
+               if areServiceManagerTags(service.Tags) {
+                       if err := deleteService(kongAdminApiUrl, service.ID); err != nil {
+                               return err
+                       }
+               }
+       }
+
+       return nil
+}
+
+func listRoutes(kongAdminApiUrl string) ([]KongRoute, error) {
+       client := resty.New()
+       resp, err := client.R().
+               Get(kongAdminApiUrl + "routes")
+
+       if err != nil {
+               return nil, err
+       }
+
+       if resp.StatusCode() != http.StatusOK {
+               err := fmt.Errorf("failed to list routes, status code %d", resp.StatusCode())
+               return nil, err
+       }
+
+       var routeResponse RouteResponse
+       err = json.Unmarshal(resp.Body(), &routeResponse)
+       if err != nil {
+               return nil, err
+       }
+
+       log.Infof("kong routes %v", routeResponse.Data)
+       return routeResponse.Data, nil
+}
+
+func listServices(kongAdminApiUrl string) ([]KongService, error) {
+       client := resty.New()
+       resp, err := client.R().Get(kongAdminApiUrl + "services")
+
+       if err != nil {
+               return nil, err
+       }
+
+       if resp.StatusCode() != http.StatusOK {
+               err := fmt.Errorf("failed to list services, status code %d", resp.StatusCode())
+               return nil, err
+       }
+
+       var serviceResponse ServiceResponse
+       err = json.Unmarshal(resp.Body(), &serviceResponse)
+       if err != nil {
+               return nil, err
+       }
+
+       log.Infof("kong services %v", serviceResponse.Data)
+       return serviceResponse.Data, nil
+}
+
+func areServiceManagerTags(tags []string) bool {
+       tagMap := make(map[string]string)
+
+       for _, tag := range tags {
+               log.Debugf("found tag %s", tag)
+               tagSlice := strings.Split(tag, ":")
+               log.Debugf("tag slice %v", tagSlice)
+               if (len(tagSlice) > 0) && (tagSlice[0] != "") {
+                       if (len(tagSlice) > 1) {
+                               tagMap[tagSlice[0]] = tagSlice[1]
+                       } else {
+                               tagMap[tagSlice[0]] = ""
+                       }
+               }
+       }
+
+       if tagMap["apfId"] == "" {
+               log.Debug("did NOT find apfId")
+               return false
+       }
+       log.Debugf("found valid apfId %s", tagMap["apfId"])
+
+       if tagMap["aefId"] == "" {
+               log.Debug("did NOT find aefId")
+               return false
+       }
+       log.Debugf("found valid aefId %s", tagMap["aefId"])
+
+       if tagMap["apiId"] == "" {
+               log.Debug("did NOT find apiId")
+               return false
+       }
+       log.Debugf("found valid apiId %s", tagMap["apiId"])
+
+       return true
+}
+
+func deleteRoute(kongAdminApiUrl string, routeID string) error {
+       log.Infof("delete kong route id %s", routeID)
+       client := resty.New()
+       resp, err := client.R().Delete(kongAdminApiUrl + "routes/" + routeID)
+
+       if err != nil {
+               return err
+       }
+
+       if resp.StatusCode() != http.StatusNoContent {
+               err := fmt.Errorf("failed to delete route %s, status code %d", routeID, resp.StatusCode())
+               return err
+       }
+
+       return nil
+}
+
+func deleteService(kongAdminApiUrl string, serviceID string) error {
+       log.Infof("delete kong service id %s", serviceID)
+       client := resty.New()
+       resp, err := client.R().Delete(kongAdminApiUrl + "services/" + serviceID)
+
+       if err != nil {
+               return err
+       }
+
+       if resp.StatusCode() != http.StatusNoContent {
+               err := fmt.Errorf("failed to delete service %s, status code %d", serviceID, resp.StatusCode())
+               return err
+       }
+
+       return nil
+}
index e678860..84d2bab 100644 (file)
@@ -82,7 +82,7 @@ func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) e
 
        newServiceAPIDescription.PrepareNewService()
 
-       statusCode, err := newServiceAPIDescription.RegisterKong(ps.KongDomain, ps.KongProtocol, ps.KongIPv4, ps.KongDataPlanePort, ps.KongControlPlanePort)
+       statusCode, err := newServiceAPIDescription.RegisterKong(ps.KongDomain, ps.KongProtocol, ps.KongIPv4, ps.KongDataPlanePort, ps.KongControlPlanePort, apfId)
        if (err != nil) || (statusCode != http.StatusCreated) {
                // We can return with http.StatusForbidden if there is a http.StatusConflict detected by Kong
                msg := err.Error()
index fe681f9..451f54b 100644 (file)
@@ -38,7 +38,13 @@ func (sd *ServiceAPIDescription) PrepareNewService() {
        sd.ApiId = &apiName
 }
 
-func (sd *ServiceAPIDescription) RegisterKong(kongDomain string, kongProtocol string, kongIPv4 common29122.Ipv4Addr, kongDataPlanePort common29122.Port, kongControlPlanePort common29122.Port) (int, error) {
+func (sd *ServiceAPIDescription) RegisterKong(kongDomain string,
+               kongProtocol string,
+               kongIPv4 common29122.Ipv4Addr,
+               kongDataPlanePort common29122.Port,
+               kongControlPlanePort common29122.Port,
+               apfId string) (int, error) {
+
        log.Trace("entering RegisterKong")
        var (
                statusCode int
@@ -46,7 +52,7 @@ func (sd *ServiceAPIDescription) RegisterKong(kongDomain string, kongProtocol st
        )
        kongControlPlaneURL := fmt.Sprintf("%s://%s:%d", kongProtocol, kongIPv4, kongControlPlanePort)
 
-       statusCode, err = sd.createKongRoutes(kongControlPlaneURL)
+       statusCode, err = sd.createKongRoutes(kongControlPlaneURL, apfId)
        if (err != nil) || (statusCode != http.StatusCreated) {
                return statusCode, err
        }
@@ -57,7 +63,7 @@ func (sd *ServiceAPIDescription) RegisterKong(kongDomain string, kongProtocol st
        return statusCode, nil
 }
 
-func (sd *ServiceAPIDescription) createKongRoutes(kongControlPlaneURL string) (int, error) {
+func (sd *ServiceAPIDescription) createKongRoutes(kongControlPlaneURL string, apfId string) (int, error) {
        log.Trace("entering createKongRoutes")
        var (
                statusCode int
@@ -72,7 +78,7 @@ func (sd *ServiceAPIDescription) createKongRoutes(kongControlPlaneURL string) (i
                for _, version := range profile.Versions {
                        log.Debugf("createKongRoutes, apiVersion \"%s\"", version.ApiVersion)
                        for _, resource := range *version.Resources {
-                               statusCode, err = sd.createKongRoute(kongControlPlaneURL, client, resource, profile.AefId, version.ApiVersion)
+                               statusCode, err = sd.createKongRoute(kongControlPlaneURL, client, resource, apfId, profile.AefId, version.ApiVersion)
                                if (err != nil) || (statusCode != http.StatusCreated) {
                                        return statusCode, err
                                }
@@ -82,32 +88,36 @@ func (sd *ServiceAPIDescription) createKongRoutes(kongControlPlaneURL string) (i
        return statusCode, nil
 }
 
-func (sd *ServiceAPIDescription) createKongRoute(kongControlPlaneURL string, client *resty.Client, resource Resource, aefId string, apiVersion string) (int, error) {
+func (sd *ServiceAPIDescription) createKongRoute(
+               kongControlPlaneURL string,
+               client *resty.Client,
+               resource Resource,
+               apfId string,
+               aefId string,
+               apiVersion string ) (int, error) {
        log.Trace("entering createKongRoute")
-       uri := resource.Uri
 
-       if apiVersion != "" {
-               if apiVersion[0] != '/' {
-                       apiVersion = "/" + apiVersion
-               }
-               if apiVersion[len(apiVersion)-1] != '/' && resource.Uri[0] != '/' {
-                       apiVersion = apiVersion + "/"
-               }
-               uri = apiVersion + resource.Uri
-       }
+       resourceName := resource.ResourceName
+       apiId := *sd.ApiId
 
-       log.Debugf("createKongRoute, uri %s", uri)
+       tags := buildTags(apfId, aefId, apiId, apiVersion, resourceName)
+       log.Debugf("createKongRoute, tags %s", tags)
+
+       serviceName := apiId + "_" + resourceName
+       routeName := serviceName
 
-       serviceName := *sd.ApiId + "_" + resource.ResourceName
        log.Debugf("createKongRoute, serviceName %s", serviceName)
+       log.Debugf("createKongRoute, routeName %s", routeName)
        log.Debugf("createKongRoute, aefId %s", aefId)
 
-       statusCode, err := sd.createKongService(kongControlPlaneURL, serviceName, uri, aefId)
+       uri := buildUriWithVersion(apiVersion, resource.Uri)
+       log.Debugf("createKongRoute, uri %s", uri)
+
+       statusCode, err := sd.createKongService(kongControlPlaneURL, serviceName, uri, tags)
        if (err != nil) || (statusCode != http.StatusCreated) {
                return statusCode, err
        }
 
-       routeName := serviceName
        kongRoutesURL := kongControlPlaneURL + "/services/" + serviceName + "/routes"
 
        // Define the route information for Kong
@@ -115,7 +125,7 @@ func (sd *ServiceAPIDescription) createKongRoute(kongControlPlaneURL string, cli
                "name":       routeName,
                "paths":      []string{uri},
                "methods":    resource.Operations,
-               "tags":       []string{aefId},
+               "tags":       tags,
                "strip_path": true,
        }
 
@@ -144,7 +154,39 @@ func (sd *ServiceAPIDescription) createKongRoute(kongControlPlaneURL string, cli
        return resp.StatusCode(), nil
 }
 
-func (sd *ServiceAPIDescription) createKongService(kongControlPlaneURL string, kongServiceName string, kongServiceUri string, aefId string) (int, error) {
+func buildUriWithVersion(apiVersion string, uri string) string {
+       if apiVersion != "" {
+               if apiVersion[0] != '/' {
+                       apiVersion = "/" + apiVersion
+               }
+               if apiVersion[len(apiVersion)-1] != '/' && uri[0] != '/' {
+                       apiVersion = apiVersion + "/"
+               }
+               uri = apiVersion + 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)
 
@@ -167,7 +209,7 @@ func (sd *ServiceAPIDescription) createKongService(kongControlPlaneURL string, k
                "port":     firstAEFProfilePort,
                "protocol": kongControlPlaneURLParsed.Scheme,
                "path":     kongServiceUri,
-               "tags":     []string{aefId},
+               "tags":     tags,
        }
 
        // Kong admin API endpoint for creating a service