From 3a24a177108c99a2fc105f221e88066d965a6e14 Mon Sep 17 00:00:00 2001 From: Jennifer-chen-rh <89648697+Jennifer-chen-rh@users.noreply.github.com> Date: Fri, 8 Mar 2024 10:47:54 -0500 Subject: [PATCH] Alarm subscription server support 1st part: (#71) 1. Start alarm subscription server, initialize the path, related registrations 2. Alarm subscription server basic resp api support for get, list, add, and delete --- .vscode/launch.json | 18 +- api/v1alpha1/orano2ims_types.go | 1 + .../bases/oran.openshift.io_orano2imses.yaml | 4 + .../server/start_alarm_subscription_server.go | 210 ++++++++++ internal/cmd/start_cmd.go | 1 + internal/controllers/orano2ims_controller.go | 54 ++- internal/controllers/utils/constants.go | 2 + .../service/alarm_subscription_handler.go | 370 ++++++++++++++++++ .../alarm_subscription_handler_test.go | 252 ++++++++++++ proxy.sh | 11 + proxy.yaml | 15 + 11 files changed, 935 insertions(+), 3 deletions(-) create mode 100644 internal/cmd/server/start_alarm_subscription_server.go create mode 100644 internal/service/alarm_subscription_handler.go create mode 100644 internal/service/alarm_subscription_handler_test.go diff --git a/.vscode/launch.json b/.vscode/launch.json index f0043d25..92ad5055 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -52,7 +52,21 @@ "--log-level=debug", "--cloud-id=6575154c-72fc-4ed8-9a87-a81885ab38bb", "--backend-url=${env:BACKEND_URL}", - "--backend-token=${env:BACKEND_TOKEN}", + "--backend-token=${env:BACKEND_TOKEN}" + ] + }, + { + "name": "start alarm-subscription-server", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}", + "args": [ + "start", + "alarm-subscription-server", + "--log-level=debug", + "--log-field=server=alarm-subscription", + "--cloud-id=6575154c-72fc-4ed8-9a87-a81885ab38bb" ] }, { @@ -79,4 +93,4 @@ "program": "${fileDirname}" } ] -} \ No newline at end of file +} diff --git a/api/v1alpha1/orano2ims_types.go b/api/v1alpha1/orano2ims_types.go index 82afc48f..f971c404 100644 --- a/api/v1alpha1/orano2ims_types.go +++ b/api/v1alpha1/orano2ims_types.go @@ -35,6 +35,7 @@ type ORANO2IMSSpec struct { DeploymentManagerServer bool `json:"deploymentManagerServer"` //+kubebuilder:default=false ResourceServer bool `json:"resourceServer"` + AlarmSubscriptionServer bool `json:"alarmSubscriptionServer"` //+optional IngressHost string `json:"ingressHost,omitempty"` //+optional diff --git a/config/crd/bases/oran.openshift.io_orano2imses.yaml b/config/crd/bases/oran.openshift.io_orano2imses.yaml index 40284518..6b484395 100644 --- a/config/crd/bases/oran.openshift.io_orano2imses.yaml +++ b/config/crd/bases/oran.openshift.io_orano2imses.yaml @@ -54,6 +54,9 @@ spec: items: type: string type: array + alarmSubscriptionServer: + default: false + type: boolean ingressHost: type: string metadataServer: @@ -67,6 +70,7 @@ spec: required: - cloudId - deploymentManagerServer + - AlarmSubscriptionServer - metadataServer - resourceServer type: object diff --git a/internal/cmd/server/start_alarm_subscription_server.go b/internal/cmd/server/start_alarm_subscription_server.go new file mode 100644 index 00000000..a5c8e979 --- /dev/null +++ b/internal/cmd/server/start_alarm_subscription_server.go @@ -0,0 +1,210 @@ +/* +Copyright 2023 Red Hat Inc. + +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. +*/ + +package server + +import ( + "log/slog" + "net/http" + + "github.com/gorilla/mux" + "github.com/spf13/cobra" + + "github.com/openshift-kni/oran-o2ims/internal" + "github.com/openshift-kni/oran-o2ims/internal/exit" + "github.com/openshift-kni/oran-o2ims/internal/logging" + "github.com/openshift-kni/oran-o2ims/internal/network" + "github.com/openshift-kni/oran-o2ims/internal/service" +) + +// Server creates and returns the `start alarm-subscription-server` command. +func AlarmSubscriptionServer() *cobra.Command { + c := NewAlarmSubscriptionServer() + result := &cobra.Command{ + Use: "alarm-subscription-server", + Short: "Starts the alarm Subscription Server", + Args: cobra.NoArgs, + RunE: c.run, + } + flags := result.Flags() + + // no need for now + //authentication.AddFlags(flags) + //authorization.AddFlags(flags) + + network.AddListenerFlags(flags, network.APIListener, network.APIAddress) + _ = flags.String( + cloudIDFlagName, + "", + "O-Cloud identifier.", + ) + _ = flags.StringArray( + extensionsFlagName, + []string{}, + "Extension to add to alarm subscriptions.", + ) + return result +} + +// alarmSubscriptionServerCommand contains the data and logic needed to run the `start +// alarm-subscription-server` command. +type AlarmSubscriptionServerCommand struct { +} + +// NewAlarmSubscriptionServer creates a new runner that knows how to execute the `start +// alarm-subscription-server` command. +func NewAlarmSubscriptionServer() *AlarmSubscriptionServerCommand { + return &AlarmSubscriptionServerCommand{} +} + +// run executes the `start alarm-subscription-server` command. +func (c *AlarmSubscriptionServerCommand) run(cmd *cobra.Command, argv []string) error { + // Get the context: + ctx := cmd.Context() + + // Get the dependencies from the context: + logger := internal.LoggerFromContext(ctx) + + // Get the flags: + flags := cmd.Flags() + + // Get the cloud identifier: + cloudID, err := flags.GetString(cloudIDFlagName) + if err != nil { + logger.Error( + "Failed to get cloud identifier flag", + "flag", cloudIDFlagName, + "error", err.Error(), + ) + return exit.Error(1) + } + if cloudID == "" { + logger.Error( + "Cloud identifier is empty", + "flag", cloudIDFlagName, + ) + return exit.Error(1) + } + logger.Info( + "Cloud identifier", + "value", cloudID, + ) + + // Get the backend details: + extensions, err := flags.GetStringArray(extensionsFlagName) + if err != nil { + logger.Error( + "Failed to extension flag", + "flag", extensionsFlagName, + "error", err.Error(), + ) + return exit.Error(1) + } + logger.Info( + "alarm subscription extensions details", + slog.Any("extensions", extensions), + ) + + // Create the logging wrapper: + loggingWrapper, err := logging.NewTransportWrapper(). + SetLogger(logger). + SetFlags(flags). + Build() + if err != nil { + logger.Error( + "Failed to create transport wrapper", + "error", err.Error(), + ) + return exit.Error(1) + } + + // Create the router: + router := mux.NewRouter() + router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + service.SendError(w, http.StatusNotFound, "Not found") + }) + router.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + service.SendError(w, http.StatusMethodNotAllowed, "Method not allowed") + }) + //router.Use(authenticationWrapper, authorizationWrapper) + + // Create the handler: + handler, err := service.NewAlarmSubscriptionHandler(). + SetLogger(logger). + SetLoggingWrapper(loggingWrapper). + SetCloudID(cloudID). + SetExtensions(extensions...). + Build() + if err != nil { + logger.Error( + "Failed to create handler", + "error", err, + ) + return exit.Error(1) + } + + // Create the routes: + adapter, err := service.NewAdapter(). + SetLogger(logger). + SetPathVariables("alarmSubscriptionID"). + SetHandler(handler). + Build() + if err != nil { + logger.Error( + "Failed to create adapter", + "error", err, + ) + return exit.Error(1) + } + router.Handle( + "/o2ims-infrastructureMonitoring/{version}/alarmSubscriptions", + adapter, + ).Methods(http.MethodGet, http.MethodPost) + + router.Handle( + "/o2ims-infrastructureMonitoring/{version}/alarmSubscriptions/{alarmSubscriptionID}", + adapter, + ).Methods(http.MethodGet, http.MethodDelete) + + // Start the API server: + apiListener, err := network.NewListener(). + SetLogger(logger). + SetFlags(flags, network.APIListener). + Build() + if err != nil { + logger.Error( + "Failed to to create API listener", + slog.String("error", err.Error()), + ) + return exit.Error(1) + } + logger.Info( + "API listening", + slog.String("address", apiListener.Addr().String()), + ) + apiServer := http.Server{ + Addr: apiListener.Addr().String(), + Handler: router, + } + err = apiServer.Serve(apiListener) + if err != nil { + logger.Error( + "API server finished with error", + slog.String("error", err.Error()), + ) + return exit.Error(1) + } + + return nil +} diff --git a/internal/cmd/start_cmd.go b/internal/cmd/start_cmd.go index 86923a32..0aa6a1e3 100644 --- a/internal/cmd/start_cmd.go +++ b/internal/cmd/start_cmd.go @@ -31,5 +31,6 @@ func Start() *cobra.Command { result.AddCommand(server.MetadataServer()) result.AddCommand(server.ResourceServer()) result.AddCommand(server.AlarmServer()) + result.AddCommand(server.AlarmSubscriptionServer()) return result } diff --git a/internal/controllers/orano2ims_controller.go b/internal/controllers/orano2ims_controller.go index 1a84db28..ac241c3e 100644 --- a/internal/controllers/orano2ims_controller.go +++ b/internal/controllers/orano2ims_controller.go @@ -85,7 +85,7 @@ func (r *ORANO2IMSReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( nextReconcile = ctrl.Result{RequeueAfter: 5 * time.Minute} // Create the needed Ingress if at least one server is required by the Spec. - if orano2ims.Spec.MetadataServer || orano2ims.Spec.DeploymentManagerServer || orano2ims.Spec.ResourceServer { + if orano2ims.Spec.MetadataServer || orano2ims.Spec.DeploymentManagerServer || orano2ims.Spec.ResourceServer || orano2ims.Spec.AlarmSubscriptionServer { err = r.createIngress(ctx, orano2ims) if err != nil { r.Log.Error(err, "Failed to deploy Ingress.") @@ -178,6 +178,43 @@ func (r *ORANO2IMSReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return } } + // Start the alert subscription server if required by the Spec. + if orano2ims.Spec.AlarmSubscriptionServer { + // Create the client ServiceAccount. + err = r.createServiceAccount(ctx, orano2ims, utils.ORANO2IMSClientSAName) + if err != nil { + r.Log.Error(err, fmt.Sprintf("Failed to deploy ServiceAccount %s for alert subscription server.", utils.ORANO2IMSClientSAName)) + return + } + + // Create authz ConfigMap. + err = r.createConfigMap(ctx, orano2ims, utils.ORANO2IMSConfigMapName) + if err != nil { + r.Log.Error(err, "Failed to deploy ConfigMap for alert subscription server.") + return + } + + // Create the needed ServiceAccount. + err = r.createServiceAccount(ctx, orano2ims, utils.ORANO2IMSAlarmSubscriptionServerName) + if err != nil { + r.Log.Error(err, "Failed to deploy ServiceAccount for Alarm Subscription server.") + return + } + + // Create the Service needed for the alert subscription server. + err = r.createService(ctx, orano2ims, utils.ORANO2IMSAlarmSubscriptionServerName) + if err != nil { + r.Log.Error(err, "Failed to deploy Service for Alarm Subscription server.") + return + } + + // Create the alert subscription-server deployment. + err = r.deployServer(ctx, orano2ims, utils.ORANO2IMSAlarmSubscriptionServerName) + if err != nil { + r.Log.Error(err, "Failed to deploy the alert subscription server.") + return + } + } err = r.updateORANO2ISMStatus(ctx, orano2ims) if err != nil { @@ -412,6 +449,21 @@ func (r *ORANO2IMSReconciler) createIngress(ctx context.Context, orano2ims *oran }, }, }, + { + Path: "/o2ims-infrastructureMonitoring/v1/alertSubscriptions", + PathType: func() *networkingv1.PathType { + pathType := networkingv1.PathTypePrefix + return &pathType + }(), + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "alert-subscription-server", + Port: networkingv1.ServiceBackendPort{ + Name: utils.ORANO2IMSIngressName, + }, + }, + }, + }, }, }, }, diff --git a/internal/controllers/utils/constants.go b/internal/controllers/utils/constants.go index 3c62d7c5..07b6506a 100644 --- a/internal/controllers/utils/constants.go +++ b/internal/controllers/utils/constants.go @@ -15,6 +15,7 @@ const ( ORANO2IMSMetadata = "metadata" ORANO2IMSDeploymentManager = "deployment-manager" ORANO2IMSResource = "resource" + ORANO2IMSAlarmSubscription = "alarm-subscription" ) // Deployment names @@ -22,6 +23,7 @@ const ( ORANO2IMSMetadataServerName = ORANO2IMSMetadata + "-server" ORANO2IMSDeploymentManagerServerName = ORANO2IMSDeploymentManager + "-server" ORANO2IMSResourceServerName = ORANO2IMSResource + "-server" + ORANO2IMSAlarmSubscriptionServerName = ORANO2IMSAlarmSubscription + "-server" ) // CR default names diff --git a/internal/service/alarm_subscription_handler.go b/internal/service/alarm_subscription_handler.go new file mode 100644 index 00000000..91becf85 --- /dev/null +++ b/internal/service/alarm_subscription_handler.go @@ -0,0 +1,370 @@ +/* +Copyright 2023 Red Hat Inc. + +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. +*/ + +package service + +import ( + "context" + "errors" + "log/slog" + "net/http" + "slices" + "sync" + + "github.com/google/uuid" + jsoniter "github.com/json-iterator/go" + "github.com/openshift-kni/oran-o2ims/internal/data" + "github.com/openshift-kni/oran-o2ims/internal/jq" + "github.com/openshift-kni/oran-o2ims/internal/search" + "golang.org/x/exp/maps" +) + +// DeploymentManagerHandlerBuilder contains the data and logic needed to create a new deployment +// manager collection handler. Don't create instances of this type directly, use the +// NewDeploymentManagerHandler function instead. +type alarmSubscriptionHandlerBuilder struct { + logger *slog.Logger + loggingWrapper func(http.RoundTripper) http.RoundTripper + cloudID string + extensions []string +} + +// alarmSubscriptionHander knows how to respond to requests to list deployment managers. +// Don't create instances of this type directly, use the NewAlarmSubscriptionHandler function +// instead. +type alarmSubscriptionHandler struct { + logger *slog.Logger + loggingWrapper func(http.RoundTripper) http.RoundTripper + cloudID string + extensions []string + jsonAPI jsoniter.API + selectorEvaluator *search.SelectorEvaluator + jqTool *jq.Tool + subscritionMapMemoryLock *sync.Mutex + subscriptionMap map[string]data.Object +} + +// NewAlarmSubscriptionHandler creates a builder that can then be used to configure and create a +// handler for the collection of deployment managers. +func NewAlarmSubscriptionHandler() *alarmSubscriptionHandlerBuilder { + return &alarmSubscriptionHandlerBuilder{} +} + +// SetLogger sets the logger that the handler will use to write to the log. This is mandatory. +func (b *alarmSubscriptionHandlerBuilder) SetLogger( + value *slog.Logger) *alarmSubscriptionHandlerBuilder { + b.logger = value + return b +} + +// SetLoggingWrapper sets the wrapper that will be used to configure logging for the HTTP clients +// used to connect to other servers, including the backend server. This is optional. +func (b *alarmSubscriptionHandlerBuilder) SetLoggingWrapper( + value func(http.RoundTripper) http.RoundTripper) *alarmSubscriptionHandlerBuilder { + b.loggingWrapper = value + return b +} + +// SetCloudID sets the identifier of the O-Cloud of this handler. This is mandatory. +func (b *alarmSubscriptionHandlerBuilder) SetCloudID( + value string) *alarmSubscriptionHandlerBuilder { + b.cloudID = value + return b +} + +// SetExtensions sets the fields that will be added to the extensions. +func (b *alarmSubscriptionHandlerBuilder) SetExtensions( + values ...string) *alarmSubscriptionHandlerBuilder { + b.extensions = values + return b +} + +// Build uses the data stored in the builder to create anad configure a new handler. +func (b *alarmSubscriptionHandlerBuilder) Build() ( + result *alarmSubscriptionHandler, err error) { + // Check parameters: + if b.logger == nil { + err = errors.New("logger is mandatory") + return + } + if b.cloudID == "" { + err = errors.New("cloud identifier is mandatory") + return + } + + // Prepare the JSON iterator API: + jsonConfig := jsoniter.Config{ + IndentionStep: 2, + } + jsonAPI := jsonConfig.Froze() + + // Create the filter expression evaluator: + pathEvaluator, err := search.NewPathEvaluator(). + SetLogger(b.logger). + Build() + if err != nil { + return + } + selectorEvaluator, err := search.NewSelectorEvaluator(). + SetLogger(b.logger). + SetPathEvaluator(pathEvaluator.Evaluate). + Build() + if err != nil { + return + } + + // Create the jq tool: + jqTool, err := jq.NewTool(). + SetLogger(b.logger). + Build() + if err != nil { + return + } + + // Check that extensions are at least syntactically valid: + for _, extension := range b.extensions { + _, err = jqTool.Compile(extension) + if err != nil { + return + } + } + + // Create and populate the object: + result = &alarmSubscriptionHandler{ + logger: b.logger, + loggingWrapper: b.loggingWrapper, + cloudID: b.cloudID, + extensions: slices.Clone(b.extensions), + selectorEvaluator: selectorEvaluator, + jsonAPI: jsonAPI, + jqTool: jqTool, + subscritionMapMemoryLock: &sync.Mutex{}, + subscriptionMap: map[string]data.Object{}, + } + + b.logger.Debug( + "alarmSubscriptionHandler build:", + "CloudID", b.cloudID, + ) + + return +} + +// List is the implementation of the collection handler interface. +func (h *alarmSubscriptionHandler) List(ctx context.Context, + request *ListRequest) (response *ListResponse, err error) { + // Create the stream that will fetch the items: + var items data.Stream + + items, err = h.fetchItems(ctx) + + if err != nil { + return + } + + // Transform the items into what we need: + items = data.Map(items, h.mapItem) + + // Select only the items that satisfy the filter: + if request.Selector != nil { + items = data.Select( + items, + func(ctx context.Context, item data.Object) (result bool, err error) { + result, err = h.selectorEvaluator.Evaluate(ctx, request.Selector, item) + return + }, + ) + } + + // Return the result: + response = &ListResponse{ + Items: items, + } + return +} + +/* to be deleted +func (h *alarmSubscriptionHandler) RetrieveSubscriptionMapValue( + request *GetRequest) (item data.Object, err error) { + h.subscritionMapMemoryLock.Lock() + defer h.subscritionMapMemoryLock.Unlock() + item, ok := h.subscriptionMap[request.Variables[0]] + if !ok { + err = ErrNotFound + return + } + return +} +*/ + +// Get is the implementation of the object handler interface. +func (h *alarmSubscriptionHandler) Get(ctx context.Context, + request *GetRequest) (response *GetResponse, err error) { + + h.logger.Debug( + "alarmSubscriptionHandler Get:", + ) + item, err := h.fetchItem(ctx, request.Variables[0]) + + // Return the result: + response = &GetResponse{ + Object: item, + } + return +} + +// Add is the implementation of the object handler ADD interface. +func (h *alarmSubscriptionHandler) Add(ctx context.Context, + request *AddRequest) (response *AddResponse, err error) { + + h.logger.Debug( + "alarmSubscriptionHandler Add:", + ) + id, err := h.addItem(ctx, *request) + + if err != nil { + return + } + + //add subscription Id in the response + obj := request.Object + + obj, err = h.encodeSubId(ctx, id, obj) + + if err != nil { + return + } + + // Return the result: + response = &AddResponse{ + Object: obj, + } + return +} + +// Delete is the implementation of the object handler delete interface. +func (h *alarmSubscriptionHandler) Delete(ctx context.Context, + request *DeleteRequest) (response *DeleteResponse, err error) { + + h.logger.Debug( + "alarmSubscriptionHandler delete:", + ) + err = h.deleteItem(ctx, *request) + + // Return the result: + response = &DeleteResponse{} + + return +} +func (h *alarmSubscriptionHandler) fetchItem(ctx context.Context, + id string) (result data.Object, err error) { + h.subscritionMapMemoryLock.Lock() + defer h.subscritionMapMemoryLock.Unlock() + result, ok := h.subscriptionMap[id] + if !ok { + err = ErrNotFound + return + } + return +} + +func (h *alarmSubscriptionHandler) fetchItems( + ctx context.Context) (result data.Stream, err error) { + h.subscritionMapMemoryLock.Lock() + defer h.subscritionMapMemoryLock.Unlock() + ar := maps.Values(h.subscriptionMap) + h.logger.Debug( + "alarmSubscriptionHandler fetchItems:", + ) + result = data.Pour(ar...) + return +} + +func (h *alarmSubscriptionHandler) getSubcriptionId() (subId string) { + subId = uuid.New().String() + return +} + +// Not sure if we need this in the future +// add it for now for the test purpose +func (h *alarmSubscriptionHandler) encodeSubId(ctx context.Context, + subId string, input data.Object) (output data.Object, err error) { + //get cluster name, subscriptions + err = h.jqTool.Evaluate( + `{ + "alarmSubscriptionId": $alarmSubId, + "clustomerId": .customerId, + "filter": .filter + }`, + input, &output, + jq.String("$alarmSubId", subId), + ) + if err != nil { + return + } + return +} + +func (h *alarmSubscriptionHandler) decodeSubId(ctx context.Context, + input data.Object) (output string, err error) { + //get cluster name, subscriptions + err = h.jqTool.Evaluate( + `.alarmSubscriptionId`, input, &output) + if err != nil { + return + } + return +} +func (h *alarmSubscriptionHandler) addItem( + ctx context.Context, input_data AddRequest) (subId string, err error) { + + subId = h.getSubcriptionId() + object, err := h.encodeSubId(ctx, subId, input_data.Object) + if err != nil { + return + } + + object, err = h.mapItem(ctx, object) + h.subscritionMapMemoryLock.Lock() + defer h.subscritionMapMemoryLock.Unlock() + h.subscriptionMap[subId] = object + + return +} + +func (h *alarmSubscriptionHandler) deleteItem( + ctx context.Context, delete_req DeleteRequest) (err error) { + + h.subscritionMapMemoryLock.Lock() + defer h.subscritionMapMemoryLock.Unlock() + + //test if the key in the map + _, ok := h.subscriptionMap[delete_req.Variables[0]] + + if !ok { + err = ErrNotFound + return + } + + delete(h.subscriptionMap, delete_req.Variables[0]) + + return +} + +func (h *alarmSubscriptionHandler) mapItem(ctx context.Context, + input data.Object) (output data.Object, err error) { + + //TBD only save related attributes in the future + return input, nil +} diff --git a/internal/service/alarm_subscription_handler_test.go b/internal/service/alarm_subscription_handler_test.go new file mode 100644 index 00000000..1f7078d8 --- /dev/null +++ b/internal/service/alarm_subscription_handler_test.go @@ -0,0 +1,252 @@ +/* +Copyright (c) 2023 Red Hat, Inc. + +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. +*/ + +package service + +import ( + "context" + + . "github.com/onsi/ginkgo/v2/dsl/core" + . "github.com/onsi/gomega" + "github.com/openshift-kni/oran-o2ims/internal/data" +) + +var _ = Describe("alarm Subscription handler", func() { + Describe("Creation", func() { + It("Can't be created without a logger", func() { + handler, err := NewAlarmSubscriptionHandler(). + SetCloudID("123"). + Build() + Expect(err).To(HaveOccurred()) + Expect(handler).To(BeNil()) + msg := err.Error() + Expect(msg).To(ContainSubstring("logger")) + Expect(msg).To(ContainSubstring("mandatory")) + }) + + It("Can't be created without a cloud identifier", func() { + handler, err := NewAlarmSubscriptionHandler(). + SetLogger(logger). + Build() + Expect(err).To(HaveOccurred()) + Expect(handler).To(BeNil()) + msg := err.Error() + Expect(msg).To(ContainSubstring("cloud identifier")) + Expect(msg).To(ContainSubstring("mandatory")) + }) + }) + + Describe("Behaviour", func() { + var ( + ctx context.Context + ) + + BeforeEach(func() { + // Create a context: + ctx = context.Background() + + }) + + Describe("List", func() { + + It("Translates empty list of results", func() { + + // Create the handler: + handler, err := NewAlarmSubscriptionHandler(). + SetLogger(logger). + SetCloudID("123"). + Build() + Expect(err).ToNot(HaveOccurred()) + Expect(handler).ToNot(BeNil()) + + // Send the request and verify the result: + response, err := handler.List(ctx, &ListRequest{}) + Expect(err).ToNot(HaveOccurred()) + Expect(response).ToNot(BeNil()) + items, err := data.Collect(ctx, response.Items) + Expect(err).ToNot(HaveOccurred()) + Expect(items).To(BeEmpty()) + }) + + It("Translates non empty list of results", func() { + // Create the handler: + handler, err := NewAlarmSubscriptionHandler(). + SetLogger(logger). + SetCloudID("123"). + Build() + Expect(err).ToNot(HaveOccurred()) + Expect(handler).ToNot(BeNil()) + + // pre-populate the subscription map + obj_1 := data.Object{ + "customerId": "test_customer_id_prime", + } + obj_2 := data.Object{ + "customerId": "test_custer_id", + "filter": data.Object{ + "notificationType": "1", + "nsInstanceId": "test_instance_id", + "status": "active", + }, + } + req_1 := AddRequest{nil, obj_1} + req_2 := AddRequest{nil, obj_2} + + subId_1, err := handler.addItem(ctx, req_1) + Expect(err).ToNot(HaveOccurred()) + + subId_2, err := handler.addItem(ctx, req_2) + Expect(err).ToNot(HaveOccurred()) + + obj_1, err = handler.encodeSubId(ctx, subId_1, obj_1) + Expect(err).ToNot(HaveOccurred()) + obj_2, err = handler.encodeSubId(ctx, subId_2, obj_2) + Expect(err).ToNot(HaveOccurred()) + + subIdMap := map[string]data.Object{} + subIdMap[subId_2] = obj_2 + subIdMap[subId_1] = obj_1 + + //subIdArray := maps.Keys(subIdMap) + + // Send the request and verify the result: + response, err := handler.List(ctx, &ListRequest{}) + Expect(err).ToNot(HaveOccurred()) + Expect(response).ToNot(BeNil()) + items, err := data.Collect(ctx, response.Items) + Expect(err).ToNot(HaveOccurred()) + Expect(items).To(HaveLen(2)) + id, err := handler.decodeSubId(ctx, items[0]) + Expect(err).ToNot(HaveOccurred()) + Expect(items[0]).To(Equal(subIdMap[id])) + id, err = handler.decodeSubId(ctx, items[1]) + Expect(err).ToNot(HaveOccurred()) + Expect(items[1]).To(Equal(subIdMap[id])) + }) + + /* tbd + It("Adds configurable extensions", func() { + }) */ + }) + + Describe("Get", func() { + It("Test Get functions", func() { + // Create the handler: + handler, err := NewAlarmSubscriptionHandler(). + SetLogger(logger). + SetCloudID("123"). + Build() + Expect(err).ToNot(HaveOccurred()) + + // Send the request. Note that we ignore the error here because + // all we care about in this test is that it sends the token, no + // matter what is the response. + // Send fake/wrong Id + resp, err := handler.Get(ctx, &GetRequest{ + Variables: []string{"negtive_test"}, + }) + msg := err.Error() + Expect(msg).To(Equal("not found")) + Expect(resp.Object).To(BeEmpty()) + + }) + + It("Uses the right search id ", func() { + // Create the handler: + handler, err := NewAlarmSubscriptionHandler(). + SetLogger(logger). + SetCloudID("123"). + Build() + Expect(err).ToNot(HaveOccurred()) + obj_1 := data.Object{ + "customerId": "test_custer_id", + "filter": data.Object{ + "notificationType": "1", + "nsInstanceId": "test_instance_id", + "status": "active", + }, + } + req_1 := AddRequest{nil, obj_1} + + subId_1, err := handler.addItem(ctx, req_1) + Expect(err).ToNot(HaveOccurred()) + obj_1, err = handler.encodeSubId(ctx, subId_1, obj_1) + Expect(err).ToNot(HaveOccurred()) + + // Send the request. Note that we ignore the error here because + // all we care about in this test is that it uses the right URL + // path, no matter what is the response. + resp, err := handler.Get(ctx, &GetRequest{ + Variables: []string{subId_1}, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(resp.Object).To(Equal(obj_1)) + }) + + }) + + Describe("Add + Delete", func() { + It("Create the alart subscription and add a subscription", func() { + // Create the handler: + handler, err := NewAlarmSubscriptionHandler(). + SetLogger(logger). + SetCloudID("123"). + Build() + Expect(err).ToNot(HaveOccurred()) + obj := data.Object{ + "customerId": "test_custer_id", + "filter": data.Object{ + "notificationType": "1", + "nsInstanceId": "test_instance_id", + "status": "active", + }, + } + + //add the request + add_req := AddRequest{nil, obj} + resp, err := handler.Add(ctx, &add_req) + Expect(err).ToNot(HaveOccurred()) + + //decode the subId + sub_id, err := handler.decodeSubId(ctx, resp.Object) + Expect(err).ToNot(HaveOccurred()) + + //use Get to verify the addrequest + get_resp, err := handler.Get(ctx, &GetRequest{ + Variables: []string{sub_id}, + }) + Expect(err).ToNot(HaveOccurred()) + //extract sub_id and verify + sub_id_get, err := handler.decodeSubId(ctx, get_resp.Object) + Expect(err).ToNot(HaveOccurred()) + Expect(sub_id).To(Equal(sub_id_get)) + + //use Delete + _, err = handler.Delete(ctx, &DeleteRequest{ + Variables: []string{sub_id}}) + Expect(err).ToNot(HaveOccurred()) + + //use Get to verify the entry was deleted + get_resp, err = handler.Get(ctx, &GetRequest{ + Variables: []string{sub_id}, + }) + + msg := err.Error() + Expect(msg).To(Equal("not found")) + Expect(get_resp.Object).To(BeEmpty()) + }) + + }) + }) +}) diff --git a/proxy.sh b/proxy.sh index f401e34a..2c8f2c34 100755 --- a/proxy.sh +++ b/proxy.sh @@ -60,6 +60,17 @@ pids="${pids} $!" & pids="${pids} $!" +# Start the alert subscription server: +./oran-o2ims start alarm-subscription-server \ +--log-file="servers.log" \ +--log-level="debug" \ +--log-field="server=alarm-subscription" \ +--log-field="pid=%p" \ +--api-listener-address="127.0.0.1:8000" \ +--cloud-id="123" \ +& +pids="${pids} $!" + # Start the reverse proxy: podman run \ --rm \ diff --git a/proxy.yaml b/proxy.yaml index 81ffa354..3ea8db9d 100644 --- a/proxy.yaml +++ b/proxy.yaml @@ -69,6 +69,21 @@ static_resources: address: 127.0.0.1 port_value: 8002 + - name: alert-subscription-server + connect_timeout: 1s + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: alert-subscription-server + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8001 + + listeners: - name: ingress