NONRTRIC-944: ServiceManager - Add kongclearup to Docker image
Issue-ID: NONRTRIC-944
Change-Id: I9c959d6930070acde22d59d3df3248ba125f8f68
Signed-off-by: DenisGNoonan <denis.noonan@est.tech>
diff --git a/.gitignore b/.gitignore
index 0dc4f08..8d0a84f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,8 +7,9 @@
.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 @@
!.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 @@
COPY . .
RUN go build -o /servicemanager
+WORKDIR "/app/internal"
+RUN go build kongclearup.go
+
##
## Deploy
##
@@ -41,5 +44,6 @@
## 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 @@
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 @@
- 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 @@
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 @@
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 @@
)
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 @@
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 @@
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 @@
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 := *sd.ApiId + "_" + resource.ResourceName
+ serviceName := apiId + "_" + resourceName
+ routeName := serviceName
+
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 @@
"name": routeName,
"paths": []string{uri},
"methods": resource.Operations,
- "tags": []string{aefId},
+ "tags": tags,
"strip_path": true,
}
@@ -144,7 +154,39 @@
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 @@
"port": firstAEFProfilePort,
"protocol": kongControlPlaneURLParsed.Scheme,
"path": kongServiceUri,
- "tags": []string{aefId},
+ "tags": tags,
}
// Kong admin API endpoint for creating a service