From f2adad008c37b48e3386dfb7cf3949d7175f96b0 Mon Sep 17 00:00:00 2001 From: DenisGNoonan Date: Thu, 21 Mar 2024 09:29:57 +0000 Subject: [PATCH] NONRTRIC-944: ServiceManager - Add kongclearup to Docker image Issue-ID: NONRTRIC-944 Change-Id: I9c959d6930070acde22d59d3df3248ba125f8f68 Signed-off-by: DenisGNoonan --- .gitignore | 4 +- servicemanager/Dockerfile | 4 + servicemanager/README.md | 17 +- servicemanager/internal/kongclear/kongclear.go | 431 +++++++++++---------- .../internal/publishservice/publishservice.go | 2 +- .../internal/publishserviceapi/typeupdate.go | 86 ++-- 6 files changed, 324 insertions(+), 220 deletions(-) diff --git a/.gitignore b/.gitignore index 0dc4f08..8d0a84f 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/servicemanager/Dockerfile b/servicemanager/Dockerfile index ddb23ed..85ef603 100644 --- a/servicemanager/Dockerfile +++ b/servicemanager/Dockerfile @@ -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"] diff --git a/servicemanager/README.md b/servicemanager/README.md index a43d9de..4db3c8d 100644 --- a/servicemanager/README.md +++ b/servicemanager/README.md @@ -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. diff --git a/servicemanager/internal/kongclear/kongclear.go b/servicemanager/internal/kongclear/kongclear.go index 068cd7d..6ec3f08 100644 --- a/servicemanager/internal/kongclear/kongclear.go +++ b/servicemanager/internal/kongclear/kongclear.go @@ -1,194 +1,237 @@ -// - -// ========================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" - - log "github.com/sirupsen/logrus" - resty "github.com/go-resty/resty/v2" -) - -// Service represents the structure for Kong service creation -type KongService struct { - ID string `json:"id"` - Name string `json:"name"` - URL string `json:"url"` -} - -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"` -} - -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("attempting to delete all Kong routes and services") - - kongAdminApiUrl := fmt.Sprintf("%s://%s:%d/", myEnv["KONG_PROTOCOL"], myEnv["KONG_IPV4"], myPorts["KONG_CONTROL_PLANE_PORT"]) - - err := deleteAllRoutes(kongAdminApiUrl) - if err != nil { - log.Fatalf("error deleting routes %v", err) - return err - } - - err = deleteAllServices(kongAdminApiUrl) - if err != nil { - log.Fatalf("error deleting services %v", err) - return err - } - - log.Info("all Kong routes and services deleted successfully") - return err -} - - -func deleteAllRoutes(kongAdminApiUrl string) error { - routes, err := listRoutes(kongAdminApiUrl) - if err != nil { - return err - } - - for _, route := range routes { - if err := deleteRoute(kongAdminApiUrl, route.ID); err != nil { - return err - } - } - - return nil -} - -func deleteAllServices(kongAdminApiUrl string) error { - services, err := listServices(kongAdminApiUrl) - if err != nil { - return err - } - - for _, service := range services { - if err := deleteService(kongAdminApiUrl, service.ID); err != nil { - return err - } - } - - return 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 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 deleteService(kongAdminApiUrl string, serviceID string) error { - log.Tracef("entering deleteService service ID %v", 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 -} - -func deleteRoute(kongAdminApiUrl string, routeID string) error { - log.Infof("kong route id %v", 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 -} \ 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 +} diff --git a/servicemanager/internal/publishservice/publishservice.go b/servicemanager/internal/publishservice/publishservice.go index e678860..84d2bab 100644 --- a/servicemanager/internal/publishservice/publishservice.go +++ b/servicemanager/internal/publishservice/publishservice.go @@ -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() diff --git a/servicemanager/internal/publishserviceapi/typeupdate.go b/servicemanager/internal/publishserviceapi/typeupdate.go index fe681f9..451f54b 100644 --- a/servicemanager/internal/publishserviceapi/typeupdate.go +++ b/servicemanager/internal/publishserviceapi/typeupdate.go @@ -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 -- 2.16.6