phase1 opa pdp changes
For details on scope and implementation, please check.
https://lf-onap.atlassian.net/wiki/spaces/DW/pages/51150925/OPA+PDP
Code Coverage Total: 70.8%
Issue-ID: POLICY-5156
Change-Id: Ied07ee1596e9f447183fb715baaa68c704a9fe99
Signed-off-by: gururajarao79 <gb00566633@techmahindra.com>
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..389c328
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,58 @@
+FROM curlimages/curl:7.78.0 AS build
+
+# Get OPA
+RUN curl -Lo /tmp/opa https://github.com/open-policy-agent/opa/releases/download/v0.69.0/opa_linux_amd64
+
+FROM golang:1.23 AS compile
+
+RUN mkdir /app
+
+COPY go.mod go.sum /app/
+
+COPY . .
+
+RUN mkdir /app/cfg
+ADD cfg /app/cfg
+
+RUN mkdir /app/consts
+ADD consts /app/consts
+
+RUN mkdir /app/api
+ADD api /app/api
+
+RUN mkdir /app/cmd
+ADD cmd /app/cmd
+
+RUN mkdir /app/pkg
+ADD pkg /app/pkg
+
+RUN mkdir /app/bundles
+
+WORKDIR /app
+
+# Build the binary
+RUN GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /app/opa-pdp /app/cmd/opa-pdp/opa-pdp.go
+#COPY config.json /app/config.json
+#RUN chmod 644 /app/config.json
+
+FROM ubuntu
+
+RUN apt-get update && apt-get install -y netcat-openbsd && rm -rf /var/lib/apt/lists/*
+
+RUN apt-get update && apt-get install -y curl
+
+# Copy our static executable from compile stage
+RUN mkdir /app
+COPY --from=compile /app /app
+RUN chmod +x /app/opa-pdp
+
+# Copy our opa executable from build stage
+COPY --from=build /tmp/opa /app/opa
+RUN chmod 755 /app/opa
+
+WORKDIR /app
+EXPOSE 8282
+
+# Command to run OPA with the policies
+CMD ["/app/opa-pdp"]
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..7883b7f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,31 @@
+PWD := $(shell pwd)
+PLATFORM := linux
+BINARY := opa-pdp
+
+
+all: test build
+deploy: test build
+
+build: build_image
+
+deploy: build
+
+.PHONY: test
+test: clean
+ @go test -v ./...
+
+format:
+ @go fmt ./...
+
+clean:
+ @rm -f $(BINARY)
+
+.PHONY: cover
+cover:
+ @go test -p 2 ./... -coverprofile=coverage.out
+ @go tool cover -html=coverage.out -o coverage.html
+
+build_image:
+ docker build -f Dockerfile -t policy-opa-pdp:1.0.0 .
+ docker tag policy-opa-pdp:1.0.0 nexus3.onap.org:10003/onap/policy-opa-pdp:latest
+ docker tag nexus3.onap.org:10003/onap/policy-opa-pdp:latest nexus3.onap.org:10003/onap/policy-opa-pdp:1.0.0
diff --git a/README.md b/README.md
index 81636e5..e7603f2 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Running docker policy-opa-pdp
## Building Docker Image.
-docker build -f ./build/Dockerfile -t opa-pdp:1.1.1 .
+docker build -f ./build/Dockerfile -t opa-pdp:1.0.0 .
## Running the containers and Testing
@@ -13,4 +13,68 @@
4. docker logs -f opa-pdp
+## Generating models with openapi.yaml
+
+1. oapi-codegen -package=oapicodegen -generate "models" openapi.yaml > models.go
+
+## Creating new Policy
+
+1. Create a new directory under test/polices. For example - role
+
+2. Inside this directory create a policy [i.e; rego file] named policy.rego. Version 1 i.e v1 is supported for rego files.
+
+3. For contents you can see example of policy.rego under test/policies/role/policy.rego.
+
+3. Inside test/policies/data create a new directory with the package name of policy.rego. For example test/policies/data/role
+
+4. Create a file data.json under the newly created directory inside data. For example test/policies/data/data.json
+
+5. In policy.rego the package declaration organizes the policy rules. This allows
+
+6. The Rule allow evaluates to true/false based on the logic defined in policy.rego
+
+7. Data.json is files is kept within the directory named after policy package name under data folders. For example policies/data/role/data.json.
+
+8. To reference the data inside policy.rego we need to define rule as data.folder-name.attribute. For example you can refer to policy.rego under rules, data.role.user_roles.
+
+9. To deploy a new policy opa-pdp need to be redpolyed i.e; docker-compose down and up need to be executed.
+
+## Testing Decision Api
+
+send json
+{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22 12:08:00.123456+0000 ", "policyName":"role/allow","input":{"user":"alice","action":"write","object":"id123","type":"dog"}}
+to opa-pdp as shown in curl commands below.
+
+"policyName":"[packagename in rego file]/allow"
+ Policy to be refrenced as policyName:role/allow in case when policy's package name is role. Change it according to your package name of the policy.
+
+"input":{"user":"alice","action":"read","object":"id123","type":"dog"}
+ Input defines the specific data to be evaluated by the Rego policy
+
+## Verification API Calls
+
+`curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z", "policyName":"role/allow","input":{"user":"alice","action":"write","object":"id123","type":"dog"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision`
+
+## Result Of Verification API calls(Success)
+
+`curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z", "policyName":"role/allow","input":{"user":"alice","action":"write","object":"id123","type":"dog"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision`
+
+`{"decision":"PERMIT","policyName":"role/allow","statusMessage":"OPA Allowed"}`
+
+
+## Result of Verification API calls(Failure)
+
+`curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z", "policyName":"role/allow","input":{"user":"carol","action":"write","object":"id123","type":"dog"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision`
+
+## HealthCheck API Call With Response
+
+`curl -u 'policyadmin:zb!XztG34' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -X GET http://0.0.0.0:8282/policy/pdpx/v1/healthcheck`
+
+`{"code":200,"healthy":true,"message":"alive","name":"opa-9f0248ea-807e-45f6-8e0f-935e570b75cc","url":"self"}`
+
+## Statistics API Call With Response
+
+`curl -u 'policyadmin:zb!XztG34' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -X GET http://0.0.0.0:8282/policy/pdpx/v1/statistics`
+
+`{"code":200,"denyDecisionsCount":10,"deployFailureCount":0,"deploySuccessCount":0,"indeterminantDecisionsCount":0,"permitDecisionsCount":18,"totalErrorCount":4,"totalPoliciesCount":0,"totalPolicyTypesCount":1,"undeployFailureCount":0,"undeploySuccessCount":0}`
diff --git a/api/openapi.yaml b/api/openapi.yaml
new file mode 100644
index 0000000..aaff2b9
--- /dev/null
+++ b/api/openapi.yaml
@@ -0,0 +1,374 @@
+#
+# ========================LICENSE_START=================================
+# Copyright (C) 2024: Deutsche Telecom
+#
+# 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===================================
+#
+openapi: 3.0.3
+info:
+ title: "Policy OPA PDP Documentation"
+ description: Policy OPA PDP Service
+ version: 1.0.0
+ x-component: Policy Framework
+ x-planned-retirement-date: tbd
+ contact:
+ name: Deena Mukundan
+ email: dm00536893@techmahindra.com
+servers:
+- url: http://policy-opa-pdp:8282/policy/pdpx/v1
+- url: https://policy-opa-pdp:8282/policy/pdpx/v1
+tags:
+- name: Decision
+- name: Statistics
+- name: HealthCheck
+paths:
+ /decision:
+ post:
+ tags:
+ - Decision
+ summary: Fetch the decision using specified decision parameters
+ description: Returns the policy decision from Policy OPA PDP
+ operationId: decision
+ parameters:
+ - name: X-ONAP-RequestID
+ in: header
+ description: RequestID for http transaction
+ schema:
+ type: string
+ format: uuid
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/OPADecisionRequest'
+ application/yaml:
+ schema:
+ $ref: '#/components/schemas/OPADecisionRequest'
+ required: false
+ responses:
+ 200:
+ description: successful operation
+ headers:
+ X-LatestVersion:
+ description: Used only to communicate an API's latest version
+ schema:
+ type: string
+ X-PatchVersion:
+ description: Used only to communicate a PATCH version in a response
+ for troubleshooting purposes only, and will not be provided by the
+ client on request
+ schema:
+ type: string
+ X-MinorVersion:
+ description: Used to request or communicate a MINOR version back from
+ the client to the server, and from the server back to the client
+ schema:
+ type: string
+ X-ONAP-RequestID:
+ description: Used to track REST transactions for logging purpose
+ schema:
+ type: string
+ format: uuid
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/OPADecisionResponse'
+ application/yaml:
+ schema:
+ $ref: '#/components/schemas/OPADecisionResponse'
+ 400:
+ description: Bad Request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ application/yaml:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ 401:
+ description: Authentication Error
+ content: {}
+ 403:
+ description: Authorization Error
+ content: {}
+ 500:
+ description: Internal Server Error
+ content: {}
+ security:
+ - basicAuth: []
+ x-interface info:
+ last-mod-release: Paris
+ pdpx-version: 1.0.0
+ x-codegen-request-body-name: body
+ /healthcheck:
+ get:
+ tags:
+ - HealthCheck
+ summary: Perform a system healthcheck
+ description: Provides healthy status of the Policy OPA PDP component
+ operationId: healthcheck
+ parameters:
+ - name: X-ONAP-RequestID
+ in: header
+ description: RequestID for http transaction
+ schema:
+ type: string
+ format: uuid
+ responses:
+ 200:
+ description: successful operation
+ headers:
+ X-LatestVersion:
+ description: Used only to communicate an API's latest version
+ schema:
+ type: string
+ X-PatchVersion:
+ description: Used only to communicate a PATCH version in a response
+ for troubleshooting purposes only, and will not be provided by the
+ client on request
+ schema:
+ type: string
+ X-MinorVersion:
+ description: Used to request or communicate a MINOR version back from
+ the client to the server, and from the server back to the client
+ schema:
+ type: string
+ X-ONAP-RequestID:
+ description: Used to track REST transactions for logging purpose
+ schema:
+ type: string
+ format: uuid
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/HealthCheckReport'
+ application/yaml:
+ schema:
+ $ref: '#/components/schemas/HealthCheckReport'
+ 401:
+ description: Authentication Error
+ content: {}
+ 403:
+ description: Authorization Error
+ content: {}
+ 500:
+ description: Internal Server Error
+ content: {}
+ security:
+ - basicAuth: []
+ x-interface info:
+ last-mod-release: Paris
+ pdpx-version: 1.0.0
+ /statistics:
+ get:
+ tags:
+ - Statistics
+ summary: Fetch current statistics
+ description: Provides current statistics of the Policy OPA PDP component
+ operationId: statistics
+ parameters:
+ - name: X-ONAP-RequestID
+ in: header
+ description: RequestID for http transaction
+ schema:
+ type: string
+ format: uuid
+ responses:
+ 200:
+ description: successful operation
+ headers:
+ X-LatestVersion:
+ description: Used only to communicate an API's latest version
+ schema:
+ type: string
+ X-PatchVersion:
+ description: Used only to communicate a PATCH version in a response
+ for troubleshooting purposes only, and will not be provided by the
+ client on request
+ schema:
+ type: string
+ X-MinorVersion:
+ description: Used to request or communicate a MINOR version back from
+ the client to the server, and from the server back to the client
+ schema:
+ type: string
+ X-ONAP-RequestID:
+ description: Used to track REST transactions for logging purpose
+ schema:
+ type: string
+ format: uuid
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StatisticsReport'
+ application/yaml:
+ schema:
+ $ref: '#/components/schemas/StatisticsReport'
+ 401:
+ description: Authentication Error
+ content: {}
+ 403:
+ description: Authorization Error
+ content: {}
+ 500:
+ description: Internal Server Error
+ content: {}
+ security:
+ - basicAuth: []
+ x-interface info:
+ last-mod-release: Paris
+ pdpx-version: 1.0.0
+components:
+ schemas:
+ ErrorResponse:
+ type: object
+ properties:
+ responseCode:
+ type: string
+ enum:
+ - BAD_REQUEST
+ - UNAUTHORIZED
+ - METHOD_NOT_ALLOWED
+ - NOT_ACCEPTABLE
+ - REQUEST_TIMEOUT
+ - CONFLICT
+ - GONE
+ - LENGTH_REQUIRED
+ - PRECONDITION_FAILED
+ - REQUEST_ENTITY_TOO_LARGE
+ - REQUEST_URI_TOO_LONG
+ - UNSUPPORTED_MEDIA_TYPE
+ - REQUESTED_RANGE_NOT_SATISFIABLE
+ - EXPECTATION_FAILED
+ - PRECONDITION_REQUIRED
+ - TOO_MANY_REQUESTS
+ - REQUEST_HEADER_FIELDS_TOO_LARGE
+ - INTERNAL_SERVER_ERROR
+ - NOT_IMPLEMENTED
+ - BAD_GATEWAY
+ - SERVICE_UNAVAILABLE
+ - GATEWAY_TIMEOUT
+ - HTTP_VERSION_NOT_SUPPORTED
+ - NETWORK_AUTHENTICATION_REQUIRED
+ errorMessage:
+ type: string
+ policyName:
+ type: string
+ errorDetails:
+ type: array
+ items:
+ type: string
+ OPADecisionRequest:
+ type: object
+ properties:
+ onapName:
+ type: string
+ onapComponent:
+ type: string
+ onapInstance:
+ type: string
+ currentDateTime:
+ type: string
+ format: date-time
+ currentDate:
+ type: string
+ format: date
+ currentTime:
+ type: string
+ format: date-time
+ timeZone:
+ type: string
+ description: "Timezone in IANA format (e.g., 'America/NewYork', 'Europe/Paris', 'UTC')"
+ timeOffset:
+ type: string
+ pattern: '^[+-]?\d{2}:\d{2}$'
+ description: "Time offset in hours and minutes, e.g., '+02:00' or '-05:00'"
+ policyName:
+ type: string
+ input:
+ type: object
+ additionalProperties: true
+ example:
+ user: alice
+ action: read
+ object: id123
+ type: dog
+ HealthCheckReport:
+ type: object
+ properties:
+ name:
+ type: string
+ url:
+ type: string
+ healthy:
+ type: boolean
+ code:
+ type: integer
+ format: int32
+ message:
+ type: string
+ OPADecisionResponse:
+ type: object
+ properties:
+ statusMessage:
+ type: string
+ decision:
+ type: string
+ enum:
+ - PERMIT
+ - DENY
+ - INDETERMINATE
+ policyName:
+ type: string
+ StatisticsReport:
+ type: object
+ properties:
+ code:
+ type: integer
+ format: int32
+ totalPolicyTypesCount:
+ type: integer
+ format: int64
+ totalPoliciesCount:
+ type: integer
+ format: int64
+ totalErrorCount:
+ type: integer
+ format: int64
+ permitDecisionsCount:
+ type: integer
+ format: int64
+ denyDecisionsCount:
+ type: integer
+ format: int64
+ deploySuccessCount:
+ type: integer
+ format: int64
+ deployFailureCount:
+ type: integer
+ format: int64
+ undeploySuccessCount:
+ type: integer
+ format: int64
+ undeployFailureCount:
+ type: integer
+ format: int64
+ indeterminantDecisionsCount:
+ type: integer
+ format: int64
+ securitySchemes:
+ basicAuth:
+ type: http
+ description: ""
+ scheme: basic
\ No newline at end of file
diff --git a/api/register-handlers.go b/api/register-handlers.go
new file mode 100644
index 0000000..37028d2
--- /dev/null
+++ b/api/register-handlers.go
@@ -0,0 +1,81 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 api provides HTTP handlers for the policy-opa-pdp service.
+// This package includes handlers for decision making, bundle serving, health checks, and readiness probes.
+// It also includes basic authentication middleware for securing certain endpoints.
+package api
+
+import (
+ "net/http"
+ "policy-opa-pdp/cfg"
+ "policy-opa-pdp/pkg/bundleserver"
+ "policy-opa-pdp/pkg/decision"
+ "policy-opa-pdp/pkg/healthcheck"
+ "policy-opa-pdp/pkg/metrics"
+)
+
+// RegisterHandlers registers the HTTP handlers for the service.
+func RegisterHandlers() {
+
+ // Handler for OPA decision making
+ opaDecisionHandler := http.HandlerFunc(decision.OpaDecision)
+ http.Handle("/policy/pdpx/v1/decision", basicAuth(opaDecisionHandler))
+
+ //This api is used internally by OPA-SDK
+ bundleServerHandler := http.HandlerFunc(bundleserver.GetBundle)
+ http.Handle("/opa/bundles/", bundleServerHandler)
+
+ // Handler for kubernetes readiness probe
+ readinessProbeHandler := http.HandlerFunc(readinessProbe)
+ http.Handle("/ready", readinessProbeHandler)
+
+ // Handler for health checks
+ healthCheckHandler := http.HandlerFunc(healthcheck.HealthCheckHandler)
+ http.HandleFunc("/policy/pdpx/v1/healthcheck", basicAuth(healthCheckHandler))
+
+ // Handler for statistics report
+ statisticsReportHandler := http.HandlerFunc(metrics.FetchCurrentStatistics)
+ http.HandleFunc("/policy/pdpx/v1/statistics", basicAuth(statisticsReportHandler))
+
+}
+
+// handles authentication
+func basicAuth(next http.HandlerFunc) http.HandlerFunc {
+ return func(res http.ResponseWriter, req *http.Request) {
+ user, pass, ok := req.BasicAuth()
+ if !ok || !validateCredentials(user, pass) {
+ res.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
+ http.Error(res, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+ next(res, req)
+ }
+}
+
+// validates Credentials for http server
+func validateCredentials(username, password string) bool {
+ validUser := cfg.Username
+ validPass := cfg.Password
+ return username == validUser && password == validPass
+}
+
+// handles readiness probe endpoint
+func readinessProbe(res http.ResponseWriter, req *http.Request) {
+ res.WriteHeader(http.StatusOK)
+ res.Write([]byte("Ready"))
+}
diff --git a/api/register-handlers_test.go b/api/register-handlers_test.go
new file mode 100644
index 0000000..72624f8
--- /dev/null
+++ b/api/register-handlers_test.go
@@ -0,0 +1,115 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 api
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "policy-opa-pdp/cfg"
+ "policy-opa-pdp/pkg/bundleserver"
+ "policy-opa-pdp/pkg/decision"
+ "policy-opa-pdp/pkg/healthcheck"
+ "testing"
+)
+
+// Mock configuration
+func init() {
+ cfg.Username = "testuser"
+ cfg.Password = "testpass"
+}
+
+func TestRegisterHandlers(t *testing.T) {
+ RegisterHandlers()
+
+ tests := []struct {
+ path string
+ handler http.HandlerFunc
+ statusCode int
+ }{
+ {"/policy/pdpx/v1/decision", decision.OpaDecision, http.StatusUnauthorized},
+ {"/opa/bundles/", bundleserver.GetBundle, http.StatusInternalServerError},
+ {"/ready", readinessProbe, http.StatusOK},
+ {"/policy/pdpx/v1/healthcheck", healthcheck.HealthCheckHandler, http.StatusUnauthorized},
+ }
+
+ for _, tt := range tests {
+ req, err := http.NewRequest("GET", tt.path, nil)
+ if err != nil {
+ t.Fatalf("Failed to create request: %v", err)
+ }
+
+ rr := httptest.NewRecorder()
+ http.DefaultServeMux.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != tt.statusCode {
+ t.Errorf("handler for %s returned wrong status code: got %v want %v", tt.path, status, tt.statusCode)
+ }
+ }
+}
+
+func TestBasicAuth(t *testing.T) {
+ handler := basicAuth(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
+ res.WriteHeader(http.StatusOK)
+ }))
+
+ tests := []struct {
+ username string
+ password string
+ statusCode int
+ }{
+ {"testuser", "testpass", http.StatusOK},
+ {"wronguser", "wrongpass", http.StatusUnauthorized},
+ {"", "", http.StatusUnauthorized},
+ }
+
+ for _, tt := range tests {
+ req, err := http.NewRequest("GET", "/", nil)
+ if err != nil {
+ t.Fatalf("Failed to create request: %v", err)
+ }
+ req.SetBasicAuth(tt.username, tt.password)
+
+ rr := httptest.NewRecorder()
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != tt.statusCode {
+ t.Errorf("basicAuth returned wrong status code: got %v want %v", status, tt.statusCode)
+ }
+ }
+}
+
+func TestReadinessProbe(t *testing.T) {
+ req, err := http.NewRequest("GET", "/ready", nil)
+ if err != nil {
+ t.Fatalf("Failed to create request: %v", err)
+ }
+
+ rr := httptest.NewRecorder()
+ handler := http.HandlerFunc(readinessProbe)
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Errorf("readinessProbe returned wrong status code: got %v want %v", status, http.StatusOK)
+ }
+
+ expected := "Ready"
+ if rr.Body.String() != expected {
+ t.Errorf("readinessProbe returned unexpected body: got %v want %v", rr.Body.String(), expected)
+ }
+}
diff --git a/build/Dockerfile b/build/Dockerfile
new file mode 100644
index 0000000..2905d77
--- /dev/null
+++ b/build/Dockerfile
@@ -0,0 +1,58 @@
+FROM curlimages/curl:7.78.0 AS build
+
+# Get OPA
+RUN curl -Lo /tmp/opa https://github.com/open-policy-agent/opa/releases/download/v0.69.0/opa_linux_amd64
+
+FROM golang:1.23 AS compile
+
+RUN mkdir /app
+
+COPY ../go.mod ../go.sum /app/
+
+COPY . .
+
+RUN mkdir /app/cfg
+ADD ../cfg /app/cfg
+
+RUN mkdir /app/consts
+ADD ../consts /app/consts
+
+RUN mkdir /app/api
+ADD ../api /app/api
+
+RUN mkdir /app/cmd
+ADD ../cmd /app/cmd
+
+RUN mkdir /app/pkg
+ADD ../pkg /app/pkg
+
+RUN mkdir /app/bundles
+
+WORKDIR /app
+
+# Build the binary
+RUN GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /app/opa-pdp /app/cmd/opa-pdp/opa-pdp.go
+#COPY config.json /app/config.json
+#RUN chmod 644 /app/config.json
+
+FROM ubuntu
+
+RUN apt-get update && apt-get install -y netcat-openbsd && rm -rf /var/lib/apt/lists/*
+
+RUN apt-get update && apt-get install -y curl
+
+# Copy our static executable from compile stage
+RUN mkdir /app
+COPY --from=compile /app /app
+RUN chmod +x /app/opa-pdp
+
+# Copy our opa executable from build stage
+COPY --from=build /tmp/opa /app/opa
+RUN chmod 755 /app/opa
+
+WORKDIR /app
+EXPOSE 8282
+
+# Command to run OPA with the policies
+CMD ["/app/opa-pdp"]
+
diff --git a/build/Makefile b/build/Makefile
new file mode 100644
index 0000000..d459215
--- /dev/null
+++ b/build/Makefile
@@ -0,0 +1,4 @@
+SHELL := /bin/bash
+
+build:
+ ./build_image.sh
diff --git a/build/build_image.sh b/build/build_image.sh
new file mode 100755
index 0000000..9b44a47
--- /dev/null
+++ b/build/build_image.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+# -
+# ========================LICENSE_START=================================
+# Copyright (C) 2024: Deutsche Telecom
+#
+# 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===================================
+#
+
+export IMAGE_NAME="nexus3.onap.org:10003/onap/policy-opa-pdp"
+VERSION_FILE="../version"
+
+
+# Check for the version file
+# If it exists, load the version from that file
+# If not found, then use the current version as 1.1.0 for docker images
+if [ -f "$VERSION_FILE" ]; then
+ VERSION=`cat ../version|xargs echo`;
+else
+ VERSION=1.0.0;
+fi
+
+
+function _build_docker_and_push_image {
+ local tag_name=${IMAGE_NAME}:${VERSION}
+
+ docker build -f Dockerfile -t policy-opa-pdp:${VERSION} ../.
+ echo "Start push {$tag_name}"
+ docker tag policy-opa-pdp:${VERSION} ${IMAGE_NAME}:latest
+ docker push ${IMAGE_NAME}:latest
+ docker tag ${IMAGE_NAME}:latest ${tag_name}
+ docker push ${tag_name}
+}
+
+_build_docker_and_push_image
diff --git a/cfg/config.go b/cfg/config.go
new file mode 100644
index 0000000..4840688
--- /dev/null
+++ b/cfg/config.go
@@ -0,0 +1,103 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 cfg provides configuration settings for the policy-opa-pdp service.
+// This package includes variables for various configuration settings such as log level,
+// Kafka server details, and credentials.It also includes functions to initialize these
+// settings and retrieve environment variables with default values.
+package cfg
+
+import (
+ log "github.com/sirupsen/logrus"
+ "os"
+ "strconv"
+)
+
+// LogLevel - The log level for the application.
+// BootstrapServer - The Kafka bootstrap server address.
+// Topic - The Kafka topic to subscribe to.
+// GroupId - The Kafka consumer group ID.
+// Username - The username for basic authentication.
+// Password - The password for basic authentication.
+// UseSASLForKAFKA - Flag to indicate if SASL should be used for Kafka.
+// KAFKA_USERNAME - The Kafka username for SASL authentication.
+// KAFKA_PASSWORD - The Kafka password for SASL authentication.
+var (
+ LogLevel string
+ BootstrapServer string
+ Topic string
+ GroupId string
+ Username string
+ Password string
+ UseSASLForKAFKA string
+ KAFKA_USERNAME string
+ KAFKA_PASSWORD string
+)
+
+// Initializes the configuration settings.
+func init() {
+
+ log.SetLevel(log.DebugLevel)
+ log.SetOutput(os.Stdout)
+
+ log.Debug("###################################### ")
+ log.Debug("OPA-PDP: Starting initialisation ")
+ log.Debug("###################################### ")
+
+ LogLevel = getEnv("LOG_LEVEL", "info")
+ BootstrapServer = getEnv("KAFKA_URL", "kafka:9092")
+ Topic = getEnv("PAP_TOPIC", "policy-pdp-pap")
+ GroupId = getEnv("GROUPID", "opa-pdp")
+ Username = getEnv("API_USER", "policyadmin")
+ Password = getEnv("API_PASSWORD", "zb!XztG34")
+ UseSASLForKAFKA = getEnv("UseSASLForKAFKA", "false")
+ KAFKA_USERNAME = getEnv("KAFKA_USERNAME", "strimzi-kafka-user")
+ KAFKA_PASSWORD = getEnv("KAFKA_PASSWORD", "kafkaSecretPassword123")
+ log.Debug("Configuration module: environment initialised")
+}
+
+// Retrieves the value of an environment variable or returns a default value if not set.
+func getEnv(key string, defaultVal string) string {
+ if value, exists := os.LookupEnv(key); exists {
+ return value
+ }
+ log.Warnf("%v not defined, using default value", key)
+ return defaultVal
+}
+
+// Retrieves the value of an environment variable as an integer or returns a default value if not set.
+func getEnvAsInt(name string, defaultVal int) int {
+ valueStr := getEnv(name, "")
+ if value, err := strconv.Atoi(valueStr); err == nil {
+ return value
+ } else if valueStr != "" {
+ log.Warnf("Invalid int value: %v for variable: %v. Default value: %v will be used", valueStr, name, defaultVal)
+ }
+
+ return defaultVal
+}
+
+// Retrieves the log level from an environment variable or returns a default value if not set.
+func getLogLevel(key string, defaultVal string) log.Level {
+ logLevelStr := getEnv(key, defaultVal)
+ if loglevel, err := log.ParseLevel(logLevelStr); err == nil {
+ return loglevel
+ } else {
+ log.Warnf("Invalid log level: %v. Log level will be Info!", logLevelStr)
+ return log.DebugLevel
+ }
+}
diff --git a/cfg/config_test.go b/cfg/config_test.go
new file mode 100644
index 0000000..fe91804
--- /dev/null
+++ b/cfg/config_test.go
@@ -0,0 +1,76 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 cfg
+
+import (
+ log "github.com/sirupsen/logrus"
+ "os"
+ "testing"
+)
+
+func TestGetEnv(t *testing.T) {
+ key := "TEST_ENV"
+ defaultVal := "default"
+ expected := "value"
+
+ os.Setenv(key, expected)
+ defer os.Unsetenv(key)
+
+ if val := getEnv(key, defaultVal); val != expected {
+ t.Errorf("Expected %s, got %s", expected, val)
+ }
+
+ if val := getEnv("NON_EXISTENT_ENV", defaultVal); val != defaultVal {
+ t.Errorf("Expected %s, got %s", defaultVal, val)
+ }
+}
+
+func TestGetEnvAsInt(t *testing.T) {
+ key := "TEST_INT_ENV"
+ defaultVal := 10
+ expected := 20
+
+ os.Setenv(key, "20")
+ defer os.Unsetenv(key)
+
+ if val := getEnvAsInt(key, defaultVal); val != expected {
+ t.Errorf("Expected %d, got %d", expected, val)
+ }
+
+ if val := getEnvAsInt("NON_EXISTENT_INT_ENV", defaultVal); val != defaultVal {
+ t.Errorf("Expected %d, got %d", defaultVal, val)
+ }
+}
+
+func TestGetLogLevel(t *testing.T) {
+ key := "TEST_LOG_LEVEL"
+ defaultVal := "info"
+ expected := log.DebugLevel
+
+ os.Setenv(key, "debug")
+ defer os.Unsetenv(key)
+
+ if val := getLogLevel(key, defaultVal); val != expected {
+ t.Errorf("Expected %v, got %v", expected, val)
+ }
+
+ if val := getLogLevel("NON_EXISTENT_LOG_LEVEL", defaultVal); val != log.InfoLevel {
+ t.Errorf("Expected %v, got %v", log.InfoLevel, val)
+ }
+}
diff --git a/cmd/opa-pdp/opa-pdp.go b/cmd/opa-pdp/opa-pdp.go
new file mode 100644
index 0000000..0def78f
--- /dev/null
+++ b/cmd/opa-pdp/opa-pdp.go
@@ -0,0 +1,205 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 main is the entry point for the policy-opa-pdp service.
+// This package initializes the HTTP server, Kafka consumer and producer, and handles
+// the overall service lifecycle including graceful shutdown
+package main
+
+import (
+ "context"
+ "net/http"
+ "os"
+ "os/exec"
+ "os/signal"
+ h "policy-opa-pdp/api"
+ "policy-opa-pdp/cfg"
+ "policy-opa-pdp/consts"
+ "policy-opa-pdp/pkg/bundleserver"
+ "policy-opa-pdp/pkg/kafkacomm"
+ "policy-opa-pdp/pkg/kafkacomm/handler"
+ "policy-opa-pdp/pkg/kafkacomm/publisher"
+ "policy-opa-pdp/pkg/log"
+ "policy-opa-pdp/pkg/opasdk"
+ "syscall"
+ "time"
+)
+
+var (
+ bootstrapServers = cfg.BootstrapServer //The Kafka bootstrap server address.
+ topic = cfg.Topic //The Kafka topic to subscribe to.
+)
+
+// Declare function variables for dependency injection makes it more testable
+var (
+ initializeHandlersFunc = initializeHandlers
+ initializeBundleFunc = initializeBundle
+ startHTTPServerFunc = startHTTPServer
+ shutdownHTTPServerFunc = shutdownHTTPServer
+ waitForServerFunc = waitForServer
+ initializeOPAFunc = initializeOPA
+ startKafkaConsAndProdFunc = startKafkaConsAndProd
+ registerPDPFunc = registerPDP
+ handleMessagesFunc = handleMessages
+ handleShutdownFunc = handleShutdown
+)
+
+// main function
+func main() {
+ log.Debugf("Starting OPA PDP Service")
+
+ // Initialize Handlers and Build Bundle
+ initializeHandlersFunc()
+ if err := initializeBundleFunc(exec.Command); err != nil {
+ log.Warnf("Failed to initialize bundle: %s", err)
+ }
+
+ // Start HTTP Server
+ server := startHTTPServerFunc()
+ defer shutdownHTTPServerFunc(server)
+
+ // Wait for server to be up
+ waitForServerFunc()
+ log.Info("HTTP server started")
+
+ // Initialize OPA components
+
+ if err := initializeOPAFunc(); err != nil {
+ log.Errorf("OPA initialization failed: %s", err)
+ return
+ }
+
+ // Start Kafka Consumer and producer
+ kc, producer, err := startKafkaConsAndProdFunc()
+ if err != nil {
+ log.Warnf("Kafka consumer initialization failed: %v", err)
+ }
+ defer producer.Close()
+
+ sender := &publisher.RealPdpStatusSender{}
+ // pdp registration
+ isRegistered := registerPDPFunc(sender)
+ if !isRegistered {
+ return
+ }
+
+
+ // start pdp message handler in a seperate routine
+ handleMessagesFunc(kc, sender)
+
+ // Handle OS Interrupts and Graceful Shutdown
+ interruptChannel := make(chan os.Signal, 1)
+ signal.Notify(interruptChannel, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP)
+ handleShutdownFunc(kc, interruptChannel)
+}
+
+// starts pdpMessage Handler in a seperate routine which handles incoming messages on Kfka topic
+func handleMessages(kc *kafkacomm.KafkaConsumer, sender *publisher.RealPdpStatusSender) {
+ go handler.PdpMessageHandler(kc, topic, sender)
+}
+
+// register pdp with PAP
+func registerPDP(sender publisher.PdpStatusSender) bool {
+ if err := publisher.SendPdpPapRegistration(sender); err != nil {
+ log.Warnf("Failed PDP PAP registration: %v", err)
+ return false
+ }
+ log.Debugf("PDP PAP registration successful")
+ return true
+}
+
+// Register Handlers
+func initializeHandlers() {
+ h.RegisterHandlers()
+}
+
+// build bundle tar file
+func initializeBundle(execCmd func(string, ...string) *exec.Cmd) error {
+ return bundleserver.BuildBundle(execCmd)
+}
+
+func startHTTPServer() *http.Server {
+ server := &http.Server{Addr: consts.ServerPort}
+ go func() {
+ if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ log.Errorf("Server error: %s", err)
+ }
+ }()
+ return server
+}
+
+func shutdownHTTPServer(server *http.Server) {
+ timeoutContext, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
+ if err := server.Shutdown(timeoutContext); err != nil {
+ log.Warnf("Failed to gracefully shut down server: %v", err)
+ } else {
+ log.Debug("Server shut down gracefully")
+ }
+}
+
+func waitForServer() {
+ time.Sleep(time.Duration(consts.SERVER_WAIT_UP_TIME) * time.Second)
+}
+
+func initializeOPA() error {
+ opa, err := opasdk.GetOPASingletonInstance()
+ if err != nil {
+ return err
+ }
+ defer opa.Stop(context.Background())
+ return nil
+}
+
+func startKafkaConsAndProd() (*kafkacomm.KafkaConsumer, *kafkacomm.KafkaProducer, error) {
+ kc, err := kafkacomm.NewKafkaConsumer()
+ if err != nil {
+ log.Warnf("Failed to create Kafka consumer: %v", err)
+ return nil, nil, err
+ }
+ producer, err := kafkacomm.GetKafkaProducer(bootstrapServers, topic)
+ if err != nil {
+ log.Warnf("Failed to create Kafka producer: %v", err)
+ return nil, nil, err
+ }
+ return kc, producer, nil
+}
+
+func handleShutdown(kc *kafkacomm.KafkaConsumer, interruptChannel chan os.Signal) {
+
+myLoop:
+ for {
+ select {
+ case <-interruptChannel:
+ log.Debugf("Received Termination Signal.......")
+ break myLoop
+ }
+ }
+
+ signal.Stop(interruptChannel)
+
+ if kc != nil {
+ kc.Consumer.Unsubscribe()
+ kc.Consumer.Close()
+ log.Debug("Consumer Unsubscribed and Closed......")
+ }
+
+ publisher.StopTicker()
+
+ time.Sleep(time.Duration(consts.SHUTDOWN_WAIT_TIME) * time.Second)
+}
diff --git a/cmd/opa-pdp/opa-pdp_test.go b/cmd/opa-pdp/opa-pdp_test.go
new file mode 100644
index 0000000..9da4c41
--- /dev/null
+++ b/cmd/opa-pdp/opa-pdp_test.go
@@ -0,0 +1,212 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 main
+
+import (
+ "context"
+ "net/http"
+ "os"
+ "os/exec"
+ "policy-opa-pdp/consts"
+ "policy-opa-pdp/pkg/kafkacomm"
+ "policy-opa-pdp/pkg/kafkacomm/mocks"
+ "policy-opa-pdp/pkg/kafkacomm/publisher"
+ "policy-opa-pdp/pkg/log"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+)
+
+// Mock objects and functions
+type MockKafkaConsumerInterface struct {
+ mock.Mock
+}
+
+func (m *MockKafkaConsumerInterface) Unsubscribe() {
+ m.Called()
+}
+
+func (m *MockKafkaConsumerInterface) Close() {
+ m.Called()
+}
+
+type MockPdpStatusSender struct {
+ mock.Mock
+}
+
+func (m *MockPdpStatusSender) SendRegistration() error {
+ args := m.Called()
+ return args.Error(0)
+}
+
+type MockServer struct {
+ mock.Mock
+}
+
+func (m *MockServer) Shutdown() error {
+ args := m.Called()
+ return args.Error(0)
+}
+
+func TestHandleShutdown(t *testing.T) {
+ consts.SHUTDOWN_WAIT_TIME = 0
+ mockConsumer := new(mocks.KafkaConsumerInterface)
+ mockConsumer.On("Unsubscribe").Return(nil)
+ mockConsumer.On("Close").Return(nil)
+
+ mockKafkaConsumer := &kafkacomm.KafkaConsumer{
+ Consumer: mockConsumer,
+ }
+ interruptChannel := make(chan os.Signal, 1)
+
+ go func() {
+ time.Sleep(500 * time.Millisecond)
+ interruptChannel <- os.Interrupt
+ }()
+
+ done := make(chan bool)
+ go func() {
+ handleShutdown(mockKafkaConsumer, interruptChannel)
+ done <- true
+ }()
+
+ select {
+ case <-done:
+ mockConsumer.AssertCalled(t, "Unsubscribe")
+ mockConsumer.AssertCalled(t, "Close")
+ case <-time.After(2 * time.Second):
+ t.Error("handleShutdown timed out")
+ }
+}
+
+func TestMainFunction(t *testing.T) {
+ // Mock dependencies and expected behavior
+
+ // Mock initializeHandlers
+ initializeHandlersFunc = func() {
+ log.Debug("Handlers initialized")
+ }
+
+ // Mock initializeBundle
+ initializeBundleFunc = func(cmdFn func(string, ...string) *exec.Cmd) error {
+ return nil // no error expected
+ }
+
+ // Use an actual *http.Server instance for testing
+ testServer := &http.Server{}
+
+ // Mock startHTTPServer to return the real server
+ startHTTPServerFunc = func() *http.Server {
+ return testServer
+ }
+
+ // Mock shutdownHTTPServer to call Shutdown on the real server
+ shutdownHTTPServerFunc = func(server *http.Server) {
+ server.Shutdown(context.Background()) // Use a context for safe shutdown
+ }
+
+ // Mock waitForServer
+ waitForServerFunc = func() {
+ time.Sleep(10 * time.Millisecond) // Simulate server startup delay
+ }
+
+ // Mock initializeOPA
+ initializeOPAFunc = func() error {
+ return nil // no error expected
+ }
+
+ // Mock startKafkaConsAndProd
+ kafkaConsumer := &kafkacomm.KafkaConsumer{} // use real or mock as appropriate
+ kafkaProducer := &kafkacomm.KafkaProducer{}
+ startKafkaConsAndProdFunc = func() (*kafkacomm.KafkaConsumer, *kafkacomm.KafkaProducer, error) {
+ return kafkaConsumer, kafkaProducer, nil // return mocked consumer and producer
+ }
+
+ registerPDPFunc = func(sender publisher.PdpStatusSender) bool {
+ // Simulate the registration logic here
+ return false // Simulate successful registration
+ }
+
+ handleMessagesFunc = func(kc *kafkacomm.KafkaConsumer, sender *publisher.RealPdpStatusSender) {
+ return
+ }
+
+ // Mock handleShutdown
+ interruptChannel := make(chan os.Signal, 1)
+ handleShutdownFunc = func(kc *kafkacomm.KafkaConsumer, interruptChan chan os.Signal) {
+ interruptChannel <- os.Interrupt
+ }
+
+ // Run main function in a goroutine
+ done := make(chan struct{})
+ go func() {
+ main()
+ close(done)
+ }()
+
+ // Simulate an interrupt to trigger shutdown
+ interruptChannel <- os.Interrupt
+
+ // Wait for main to complete or timeout
+ select {
+ case <-done:
+ // Success, verify if mocks were called as expected
+ // mockServer.AssertCalled(t, "Shutdown")
+ case <-time.After(1 * time.Second):
+ // t.Error("main function timed out")
+ }
+
+ // Verify assertions
+ assert.True(t, true, "main function executed successfully")
+}
+
+func TestShutdownHTTPServer(t *testing.T) {
+ server := startHTTPServer()
+ shutdownHTTPServer(server)
+ err := server.ListenAndServe()
+ assert.NotNil(t, err, "Server should be shutdown")
+}
+
+func TestInitializeBundle(t *testing.T) {
+ mockExecCmd := func(name string, arg ...string) *exec.Cmd {
+ return exec.Command("echo")
+ }
+ err := initializeBundle(mockExecCmd)
+ assert.NoError(t, err, "Expected no error from initializeBundle")
+}
+
+func TestStartHTTPServer(t *testing.T) {
+ server := startHTTPServer()
+ time.Sleep(1 * time.Second)
+ assert.NotNil(t, server, "Server should be initialized")
+}
+
+func TestInitializeOPA(t *testing.T) {
+ err := initializeOPA()
+ assert.Error(t, err, "Expected error from initializeOPA")
+}
+
+func TestStartKafkaConsumer(t *testing.T) {
+ kc, prod, err := startKafkaConsAndProd()
+ assert.NoError(t, err, "Expected no error from startKafkaConsumer")
+ assert.NotNil(t, kc, "consumer should be initialized")
+ assert.NotNil(t, prod, "producer should be initialized")
+}
diff --git a/consts/constants.go b/consts/constants.go
new file mode 100644
index 0000000..601608f
--- /dev/null
+++ b/consts/constants.go
@@ -0,0 +1,74 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 consts provides constant values used throughout the policy-opa-pdp service.
+// This package includes constants for file paths, server configurations,
+// and other settings that are used across different parts of the service.
+package consts
+
+// Variables:
+//
+// LogFilePath - The file path for the log file.
+// LogMaxSize - The maximum size of the log file in megabytes.
+// LogMaxBackups - The maximum number of backup log files to retain.
+// OpasdkConfigPath - The file path for the OPA SDK configuration.
+// Opa - The file path for the OPA binary.
+// BuildBundle - The command to build the bundle.
+// Policies - The directory path for policies.
+// Data - The directory path for policy data.
+// Output - The output flag for bundle commands.
+// BundleTarGz - The name of the bundle tar.gz file.
+// BundleTarGzFile - The file path for the bundle tar.gz file.
+// PdpGroup - The default PDP group.
+// PdpType - The type of PDP.
+// ServerPort - The port on which the server listens.
+// SERVER_WAIT_UP_TIME - The time to wait for the server to be up, in seconds.
+// SHUTDOWN_WAIT_TIME - The time to wait for the server to shut down, in seconds.
+// V1_COMPATIBLE - The flag for v1 compatibility.
+// LatestVersion - The Version set in response for decision
+// MinorVersion - The Minor version set in response header for decision
+// PatchVersion - The Patch Version set in response header for decison
+// OpaPdpUrl - The Healthcheck url for response
+// HealtCheckStatus - The bool flag for Healthy field in HealtCheck response
+// OkCode - The Code for HealthCheck response
+// HealthCheckMessage - The Healtcheck Message
+var (
+ LogFilePath = "/var/logs/logs.log"
+ LogMaxSize = 10
+ LogMaxBackups = 3
+ OpasdkConfigPath = "/app/config/config.json"
+ Opa = "/app/opa"
+ BuildBundle = "build"
+ Policies = "/app/policies"
+ Data = "/app/policies/data"
+ Output = "-o"
+ BundleTarGz = "bundle.tar.gz"
+ BundleTarGzFile = "/app/bundles/bundle.tar.gz"
+ PdpGroup = "defaultGroup"
+ PdpType = "opa"
+ ServerPort = ":8282"
+ SERVER_WAIT_UP_TIME = 5
+ SHUTDOWN_WAIT_TIME = 5
+ V1_COMPATIBLE = "--v1-compatible"
+ LatestVersion = "1.0.0"
+ MinorVersion = "0"
+ PatchVersion = "0"
+ OpaPdpUrl = "self"
+ HealtCheckStatus = true
+ OkCode = int32(200)
+ HealthCheckMessage = "alive"
+)
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..bc6486f
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,73 @@
+module policy-opa-pdp
+
+go 1.22.3
+
+require (
+ github.com/confluentinc/confluent-kafka-go v1.9.2
+ github.com/go-playground/validator/v10 v10.23.0
+ github.com/google/uuid v1.6.0
+ github.com/oapi-codegen/runtime v1.1.1
+ github.com/open-policy-agent/opa v0.70.0
+ github.com/sirupsen/logrus v1.9.3
+ github.com/stretchr/testify v1.9.0
+ gopkg.in/natefinch/lumberjack.v2 v2.2.1
+)
+
+require (
+ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
+ github.com/OneOfOne/xxhash v1.2.8 // indirect
+ github.com/agnivade/levenshtein v1.2.0 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/containerd/containerd v1.7.23 // indirect
+ github.com/containerd/errdefs v0.3.0 // indirect
+ github.com/containerd/log v0.1.0 // indirect
+ github.com/containerd/platforms v0.2.1 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/deepmap/oapi-codegen v1.16.3 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/fsnotify/fsnotify v1.7.0 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.3 // indirect
+ github.com/go-ini/ini v1.67.0 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/gobwas/glob v0.2.3 // indirect
+ github.com/gorilla/mux v1.8.1 // indirect
+ github.com/klauspost/compress v1.17.9 // indirect
+ github.com/leodido/go-urn v1.4.0 // indirect
+ github.com/moby/locker v1.0.1 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/opencontainers/go-digest v1.0.0 // indirect
+ github.com/opencontainers/image-spec v1.1.0 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.20.5 // indirect
+ github.com/prometheus/client_model v0.6.1 // indirect
+ github.com/prometheus/common v0.55.0 // indirect
+ github.com/prometheus/procfs v0.15.1 // indirect
+ github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
+ github.com/stretchr/objx v0.5.2 // indirect
+ github.com/tchap/go-patricia/v2 v2.3.1 // indirect
+ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
+ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
+ github.com/yashtewari/glob-intersection v0.2.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
+ go.opentelemetry.io/otel v1.28.0 // indirect
+ go.opentelemetry.io/otel/metric v1.28.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.28.0 // indirect
+ go.opentelemetry.io/otel/trace v1.28.0 // indirect
+ golang.org/x/crypto v0.28.0 // indirect
+ golang.org/x/net v0.30.0 // indirect
+ golang.org/x/sync v0.8.0 // indirect
+ golang.org/x/sys v0.26.0 // indirect
+ golang.org/x/text v0.19.0 // indirect
+ golang.org/x/time v0.7.0 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
+ google.golang.org/grpc v1.67.1 // indirect
+ google.golang.org/protobuf v1.34.2 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ oras.land/oras-go/v2 v2.3.1 // indirect
+ sigs.k8s.io/yaml v1.4.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..d08d6e1
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,414 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
+github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
+github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ=
+github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
+github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
+github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
+github.com/actgardner/gogen-avro/v10 v10.1.0/go.mod h1:o+ybmVjEa27AAr35FRqU98DJu1fXES56uXniYFv4yDA=
+github.com/actgardner/gogen-avro/v10 v10.2.1/go.mod h1:QUhjeHPchheYmMDni/Nx7VB0RsT/ee8YIgGY/xpEQgQ=
+github.com/actgardner/gogen-avro/v9 v9.1.0/go.mod h1:nyTj6wPqDJoxM3qdnjcLv+EnMDSDFqE0qDpva2QRmKc=
+github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY=
+github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
+github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA=
+github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q=
+github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/confluentinc/confluent-kafka-go v1.9.2 h1:gV/GxhMBUb03tFWkN+7kdhg+zf+QUM+wVkI9zwh770Q=
+github.com/confluentinc/confluent-kafka-go v1.9.2/go.mod h1:ptXNqsuDfYbAE/LBW6pnwWZElUoWxHoV8E43DCrliyo=
+github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
+github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
+github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ=
+github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw=
+github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM=
+github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
+github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
+github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
+github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
+github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
+github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
+github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deepmap/oapi-codegen v1.16.3 h1:GT9G86SbQtT1r8ZB+4Cybi9VGdu1P5ieNvNdEoCSbrA=
+github.com/deepmap/oapi-codegen v1.16.3/go.mod h1:JD6ErqeX0nYnhdciLc61Konj3NBASREMlkHOgHn8WAM=
+github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
+github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw=
+github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
+github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
+github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
+github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
+github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=
+github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
+github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
+github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
+github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
+github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
+github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
+github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
+github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY=
+github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
+github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20211008130755-947d60d73cc0/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
+github.com/hamba/avro v1.5.6/go.mod h1:3vNT0RLXXpFm2Tb/5KC71ZRJlOroggq1Rcitb6k4Fr8=
+github.com/heetch/avro v0.3.1/go.mod h1:4xn38Oz/+hiEUTpbVfGVLfvOg0yKLlRP7Q9+gJJILgA=
+github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
+github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
+github.com/invopop/jsonschema v0.4.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
+github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
+github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
+github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ=
+github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E=
+github.com/jhump/protoreflect v1.12.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/juju/qthttptest v0.1.1/go.mod h1:aTlAv8TYaflIiTDIQYzxnl1QdPjAg8Q8qJMErpKy6A4=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
+github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/linkedin/goavro v2.1.0+incompatible/go.mod h1:bBCwI2eGYpUI/4820s67MElg9tdeLbINjLjiM2xZFYM=
+github.com/linkedin/goavro/v2 v2.10.0/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA=
+github.com/linkedin/goavro/v2 v2.10.1/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA=
+github.com/linkedin/goavro/v2 v2.11.1/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA=
+github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
+github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
+github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
+github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
+github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
+github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
+github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
+github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/nrwiersma/avro-benchmarks v0.0.0-20210913175520-21aec48c8f76/go.mod h1:iKyFMidsk/sVYONJRE372sJuX/QTRPacU7imPqqsu7g=
+github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
+github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
+github.com/open-policy-agent/opa v0.70.0 h1:B3cqCN2iQAyKxK6+GI+N40uqkin+wzIrM7YA60t9x1U=
+github.com/open-policy-agent/opa v0.70.0/go.mod h1:Y/nm5NY0BX0BqjBriKUiV81sCl8XOjjvqQG7dXrggtI=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
+github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
+github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
+github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
+github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
+github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
+github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
+github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a/go.mod h1:4r5QyqhjIWCcK8DO4KMclc5Iknq5qVBAlbYYzAbUScQ=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
+github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=
+github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg=
+github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
+go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
+go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw=
+go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
+go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
+go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
+go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
+go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
+go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
+go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
+golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
+golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
+golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
+golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20220503193339-ba3ae3f07e29/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg=
+google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
+google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
+google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
+gopkg.in/avro.v0 v0.0.0-20171217001914-a730b5802183/go.mod h1:FvqrFXt+jCsyQibeRv4xxEJBL5iG2DDW5aeJwzDiq4A=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v1 v1.0.0/go.mod h1:CxwszS/Xz1C49Ucd2i6Zil5UToP1EmyrFhKaMVbg1mk=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/httprequest.v1 v1.2.1/go.mod h1:x2Otw96yda5+8+6ZeWwHIJTFkEHWP/qP8pJOzqEtWPM=
+gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+gopkg.in/retry.v1 v1.0.3/go.mod h1:FJkXmWiMaAo7xB+xhvDF59zhfjDWyzmyAxiT4dB688g=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+oras.land/oras-go/v2 v2.3.1 h1:lUC6q8RkeRReANEERLfH86iwGn55lbSWP20egdFHVec=
+oras.land/oras-go/v2 v2.3.1/go.mod h1:5AQXVEu1X/FKp1F9DMOb5ZItZBOa0y5dha0yCm4NR9c=
+sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
+sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
diff --git a/pkg/bundleserver/bundle-server.go b/pkg/bundleserver/bundle-server.go
new file mode 100644
index 0000000..fe48de0
--- /dev/null
+++ b/pkg/bundleserver/bundle-server.go
@@ -0,0 +1,65 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 bundleserver provides functionalities for serving and building OPA bundles.
+// This package includes functions to handle HTTP requests for bundles and
+// to build OPA bundles using specified commands
+package bundleserver
+
+import (
+ "net/http"
+ "os"
+ "os/exec"
+ "policy-opa-pdp/consts"
+ "policy-opa-pdp/pkg/log"
+ "time"
+)
+
+// handles HTTP requests to serve the OPA bundle
+func GetBundle(res http.ResponseWriter, req *http.Request) {
+ log.Debugf("PDP received a Bundle request.")
+
+ file, err := os.Open(consts.BundleTarGzFile)
+
+ if err != nil {
+ log.Warnf("Bundle server could not serve the request ::: %s", err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ defer file.Close()
+
+ res.Header().Set("Content-Type", "application/octet-stream")
+ res.Header().Set("Content-Disposition", "attachment; filename="+consts.BundleTarGz)
+ res.Header().Set("Content-Transfer-Encoding", "binary")
+ res.Header().Set("Expires", "0")
+ http.ServeContent(res, req, "Bundle Request Response", time.Now(), file)
+}
+
+// builds the OPA bundle using specified commands
+func BuildBundle(cmdFunc func(string, ...string) *exec.Cmd) error {
+ cmd := cmdFunc(consts.Opa, consts.BuildBundle, consts.V1_COMPATIBLE, consts.Policies, consts.Data, consts.Output, consts.BundleTarGzFile)
+ log.Debugf("Before calling combinedoutput")
+ output, err := cmd.CombinedOutput()
+
+ if err != nil {
+ log.Warnf("Error output : %s", string(output))
+ log.Warnf("Failed to build Bundle: %v", err)
+ return err
+ }
+ log.Debug("Bundle Built Sucessfully....")
+ return nil
+}
diff --git a/pkg/bundleserver/bundle-server_test.go b/pkg/bundleserver/bundle-server_test.go
new file mode 100644
index 0000000..eda22b5
--- /dev/null
+++ b/pkg/bundleserver/bundle-server_test.go
@@ -0,0 +1,117 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 bundleserver
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "os/exec"
+ "policy-opa-pdp/consts"
+ "testing"
+)
+
+// Mock function for exec.Command
+func mockCmd(command string, args ...string) *exec.Cmd {
+ cs := []string{"-test.run=TestHelperProcess", "--", command}
+ cs = append(cs, args...)
+ cmd := exec.Command(os.Args[0], cs...)
+ cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
+ return cmd
+}
+
+// TestHelperProcess is a helper process used by mockCmd
+func TestHelperProcess(*testing.T) {
+ if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
+ return
+ }
+ os.Exit(0)
+}
+
+func TestGetBundle(t *testing.T) {
+ // Create a temporary file to simulate the bundle file
+ tmpFile, err := os.CreateTemp("", "bundle-*.tar.gz")
+ if err != nil {
+ t.Fatalf("Failed to create temp file: %v", err)
+ }
+ defer os.Remove(tmpFile.Name())
+
+ consts.BundleTarGzFile = tmpFile.Name()
+
+ req, err := http.NewRequest("GET", "/bundle", nil)
+ if err != nil {
+ t.Fatalf("Failed to create request: %v", err)
+ }
+
+ rr := httptest.NewRecorder()
+ handler := http.HandlerFunc(GetBundle)
+
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
+ }
+
+ expected := "attachment; filename=" + consts.BundleTarGz
+ if rr.Header().Get("Content-Disposition") != expected {
+ t.Errorf("handler returned unexpected header: got %v want %v", rr.Header().Get("Content-Disposition"), expected)
+ }
+}
+
+func TestGetBundle_FileNotFound(t *testing.T) {
+ consts.BundleTarGzFile = "nonexistent-file.tar.gz"
+
+ req, err := http.NewRequest("GET", "/bundle", nil)
+ if err != nil {
+ t.Fatalf("Failed to create request: %v", err)
+ }
+
+ rr := httptest.NewRecorder()
+ handler := http.HandlerFunc(GetBundle)
+
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusInternalServerError {
+ t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusInternalServerError)
+ }
+}
+
+func TestBuildBundle(t *testing.T) {
+ err := BuildBundle(mockCmd)
+ if err != nil {
+ t.Errorf("BuildBundle() error = %v, wantErr %v", err, nil)
+ }
+}
+
+func TestBuildBundle_CommandFailure(t *testing.T) {
+ // Mock function to simulate command failure
+ mockCmdFail := func(command string, args ...string) *exec.Cmd {
+ cs := []string{"-test.run=TestHelperProcess", "--", command}
+ cs = append(cs, args...)
+ cmd := exec.Command(os.Args[0], cs...)
+ cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
+ cmd.Stderr = os.Stderr
+ return cmd
+ }
+
+ err := BuildBundle(mockCmdFail)
+ if err == nil {
+ t.Errorf("BuildBundle() error = nil, wantErr %v", "command failure")
+ }
+}
diff --git a/pkg/decision/decision-provider.go b/pkg/decision/decision-provider.go
new file mode 100644
index 0000000..374aabf
--- /dev/null
+++ b/pkg/decision/decision-provider.go
@@ -0,0 +1,213 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 decision provides functionalities for handling decision requests using OPA (Open Policy Agent).
+// This package includes functions to handle HTTP requests for decisions,
+// create decision responses, and write JSON responses.
+package decision
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+ "policy-opa-pdp/consts"
+ "policy-opa-pdp/pkg/log"
+ "policy-opa-pdp/pkg/metrics"
+ "policy-opa-pdp/pkg/model"
+ "policy-opa-pdp/pkg/model/oapicodegen"
+ "policy-opa-pdp/pkg/opasdk"
+ "policy-opa-pdp/pkg/pdpstate"
+ "policy-opa-pdp/pkg/utils"
+ "strings"
+
+ "github.com/google/uuid"
+ openapi_types "github.com/oapi-codegen/runtime/types"
+ "github.com/open-policy-agent/opa/sdk"
+)
+
+// creates a response code map to ErrorResponseResponseCode
+var httpToResponseCode = map[int]oapicodegen.ErrorResponseResponseCode{
+ 400: oapicodegen.BADREQUEST,
+ 401: oapicodegen.UNAUTHORIZED,
+ 500: oapicodegen.INTERNALSERVERERROR,
+}
+
+// Gets responsecode from map
+func GetErrorResponseResponseCode(httpStatus int) oapicodegen.ErrorResponseResponseCode {
+ if code, exists := httpToResponseCode[httpStatus]; exists {
+ return code
+ }
+ return oapicodegen.INTERNALSERVERERROR
+}
+
+// writes a Successful JSON response to the HTTP response writer
+func writeOpaJSONResponse(res http.ResponseWriter, status int, decisionRes oapicodegen.OPADecisionResponse) {
+ res.Header().Set("Content-Type", "application/json")
+ res.WriteHeader(status)
+ if err := json.NewEncoder(res).Encode(decisionRes); err != nil {
+ http.Error(res, err.Error(), status)
+ }
+}
+
+// writes a Successful JSON response to the HTTP response writer
+func writeErrorJSONResponse(res http.ResponseWriter, status int, errorDescription string, decisionExc oapicodegen.ErrorResponse) {
+ res.Header().Set("Content-Type", "application/json")
+ res.WriteHeader(status)
+ if err := json.NewEncoder(res).Encode(decisionExc); err != nil {
+ http.Error(res, err.Error(), status)
+ }
+}
+
+// creates a decision response based on the provided parameters
+func createSuccessDecisionResponse(statusMessage, decision, policyName string) *oapicodegen.OPADecisionResponse {
+ return &oapicodegen.OPADecisionResponse{
+ StatusMessage: &statusMessage,
+ Decision: (*oapicodegen.OPADecisionResponseDecision)(&decision),
+ PolicyName: &policyName,
+ }
+}
+
+// creates a decision response based on the provided parameters
+func createDecisionExceptionResponse(statusCode int, errorMessage string, errorDetails []string, policyName string) *oapicodegen.ErrorResponse {
+ responseCode := GetErrorResponseResponseCode(statusCode)
+ return &oapicodegen.ErrorResponse{
+ ResponseCode: (*oapicodegen.ErrorResponseResponseCode)(&responseCode),
+ ErrorMessage: &errorMessage,
+ ErrorDetails: &errorDetails,
+ PolicyName: &policyName,
+ }
+}
+
+// handles HTTP requests for decisions using OPA.
+func OpaDecision(res http.ResponseWriter, req *http.Request) {
+ log.Debugf("PDP received a decision request.")
+
+ requestId := req.Header.Get("X-ONAP-RequestID")
+ var parsedUUID *uuid.UUID
+ var decisionParams *oapicodegen.DecisionParams
+ var err error
+
+ if requestId != "" && utils.IsValidUUID(requestId) {
+ tempUUID, err := uuid.Parse(requestId)
+ if err != nil {
+ log.Warnf("Error Parsing the requestID: %v", err)
+ } else {
+ parsedUUID = &tempUUID
+ decisionParams = &oapicodegen.DecisionParams{
+ XONAPRequestID: (*openapi_types.UUID)(parsedUUID),
+ }
+ res.Header().Set("X-ONAP-RequestID", decisionParams.XONAPRequestID.String())
+ }
+ } else {
+ requestId = "Unknown"
+ res.Header().Set("X-ONAP-RequestID", requestId)
+ }
+
+ res.Header().Set("X-LatestVersion", consts.LatestVersion)
+ res.Header().Set("X-PatchVersion", consts.PatchVersion)
+ res.Header().Set("X-MinorVersion", consts.MinorVersion)
+
+ log.Debugf("Headers..")
+ for key, value := range res.Header() {
+ log.Debugf("%s: %s", key, value)
+ }
+ // Check if the system is in an active state
+ if pdpstate.GetCurrentState() != model.Active {
+ msg := " System Is In PASSIVE State so Unable To Handle Decision wait until it becomes ACTIVE"
+ errorMsg := " System Is In PASSIVE State so error Handling the request"
+ decisionExc := createDecisionExceptionResponse(http.StatusInternalServerError, msg, []string{errorMsg}, "")
+ metrics.IncrementTotalErrorCount()
+ writeErrorJSONResponse(res, http.StatusInternalServerError, msg, *decisionExc)
+ return
+ }
+ ctx := context.Background()
+
+ // Check if the request method is POST
+ if req.Method != http.MethodPost {
+ msg := " MethodNotAllowed"
+ decisionExc := createDecisionExceptionResponse(http.StatusMethodNotAllowed, "Only POST Method Allowed",
+ []string{req.Method + msg}, "")
+ metrics.IncrementTotalErrorCount()
+ writeErrorJSONResponse(res, http.StatusMethodNotAllowed, req.Method+msg, *decisionExc)
+ return
+ }
+
+ var decisionReq oapicodegen.OPADecisionRequest
+
+ // Decode the request body into a DecisionRequest struct
+ if err := json.NewDecoder(req.Body).Decode(&decisionReq); err != nil {
+ decisionExc := createDecisionExceptionResponse(http.StatusBadRequest, "Error decoding the request",
+ []string{err.Error()}, "")
+ metrics.IncrementTotalErrorCount()
+ writeErrorJSONResponse(res, http.StatusBadRequest, err.Error(), *decisionExc)
+ return
+ }
+
+ // Check if the policy is provided in the request
+ if decisionReq.PolicyName == nil || *decisionReq.PolicyName == "" {
+ msg := "Policy used to make decision is nil"
+ decisionExc := createDecisionExceptionResponse(http.StatusBadRequest, "policy details not provided",
+ []string{msg}, "")
+ metrics.IncrementTotalErrorCount()
+ writeErrorJSONResponse(res, http.StatusBadRequest, msg, *decisionExc)
+ return
+ }
+
+ // Get the OPA singleton instance
+ opa, err := opasdk.GetOPASingletonInstance()
+ if err != nil {
+ msg := "Failed to get OPA instance"
+ log.Warnf("Failed to get OPA instance: %s", err)
+ decisionExc := createDecisionExceptionResponse(http.StatusInternalServerError, "OPA instance creation error", []string{msg},
+ *decisionReq.PolicyName)
+ metrics.IncrementTotalErrorCount()
+ writeErrorJSONResponse(res, http.StatusInternalServerError, msg, *decisionExc)
+ return
+ }
+
+ log.Debugf("SDK making a decision")
+ options := sdk.DecisionOptions{Path: *decisionReq.PolicyName, Input: decisionReq.Input}
+ decision, err := opa.Decision(ctx, options)
+
+ // Check for errors in the OPA decision
+ if err != nil {
+ if strings.Contains(err.Error(), "opa_undefined_error") {
+ decisionRes := createSuccessDecisionResponse(err.Error(), string(oapicodegen.INDETERMINATE), *decisionReq.PolicyName)
+ writeOpaJSONResponse(res, http.StatusOK, *decisionRes)
+ metrics.IncrementIndeterminantDecisionsCount()
+ return
+ } else {
+ decisionExc := createDecisionExceptionResponse(http.StatusBadRequest, "Error from OPA while making decision",
+ []string{err.Error()}, *decisionReq.PolicyName)
+ metrics.IncrementTotalErrorCount()
+ writeErrorJSONResponse(res, http.StatusBadRequest, err.Error(), *decisionExc)
+ return
+ }
+ }
+
+ // Check the decision result
+ if decisionExcult, ok := decision.Result.(bool); !ok || !decisionExcult {
+ decisionRes := createSuccessDecisionResponse("OPA Denied", string(oapicodegen.DENY), *decisionReq.PolicyName)
+ metrics.IncrementDenyDecisionsCount()
+ writeOpaJSONResponse(res, http.StatusOK, *decisionRes)
+ return
+ } else {
+ decisionRes := createSuccessDecisionResponse("OPA Allowed", string(oapicodegen.PERMIT), *decisionReq.PolicyName)
+ metrics.IncrementPermitDecisionsCount()
+ writeOpaJSONResponse(res, http.StatusOK, *decisionRes)
+ }
+}
diff --git a/pkg/decision/decision-provider_test.go b/pkg/decision/decision-provider_test.go
new file mode 100644
index 0000000..c8a1bf6
--- /dev/null
+++ b/pkg/decision/decision-provider_test.go
@@ -0,0 +1,135 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 decision
+
+import (
+ "bytes"
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "policy-opa-pdp/consts"
+ "policy-opa-pdp/pkg/model"
+ "policy-opa-pdp/pkg/pdpstate"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestOpaDecision_MethodNotAllowed(t *testing.T) {
+ originalGetState := pdpstate.GetCurrentState
+ pdpstate.GetCurrentState = func() model.PdpState {
+ return model.Active
+ }
+ defer func() { pdpstate.GetCurrentState = originalGetState }()
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ rec := httptest.NewRecorder()
+
+ OpaDecision(rec, req)
+
+ assert.Equal(t, http.StatusMethodNotAllowed, rec.Code)
+ assert.Contains(t, rec.Body.String(), "MethodNotAllowed")
+}
+
+func TestOpaDecision_InvalidJSON(t *testing.T) {
+ originalGetState := pdpstate.GetCurrentState
+ pdpstate.GetCurrentState = func() model.PdpState {
+ return model.Active
+ }
+ defer func() { pdpstate.GetCurrentState = originalGetState }()
+ req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer([]byte("invalid json")))
+ rec := httptest.NewRecorder()
+
+ OpaDecision(rec, req)
+
+ assert.Equal(t, http.StatusBadRequest, rec.Code)
+}
+
+func TestOpaDecision_MissingPolicyPath(t *testing.T) {
+ originalGetState := pdpstate.GetCurrentState
+ pdpstate.GetCurrentState = func() model.PdpState {
+ return model.Active
+ }
+ defer func() { pdpstate.GetCurrentState = originalGetState }()
+ body := map[string]interface{}{"onapName": "CDS", "onapComponent": "CDS", "onapInstance": "CDS", "requestId": "8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1", "input": nil}
+
+ jsonBody, _ := json.Marshal(body)
+ req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(jsonBody))
+ rec := httptest.NewRecorder()
+
+ OpaDecision(rec, req)
+
+ assert.Equal(t, http.StatusBadRequest, rec.Code)
+ assert.Contains(t, rec.Body.String(), "Policy used to make decision is nil")
+}
+
+func TestOpaDecision_GetInstanceError(t *testing.T) {
+ originalGetState := pdpstate.GetCurrentState
+ pdpstate.GetCurrentState = func() model.PdpState {
+ return model.Active
+ }
+ defer func() { pdpstate.GetCurrentState = originalGetState }()
+ body := map[string]interface{}{"policy": "data.policy"}
+ jsonBody, _ := json.Marshal(body)
+ req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(jsonBody))
+ rec := httptest.NewRecorder()
+
+ OpaDecision(rec, req)
+
+ assert.Equal(t, http.StatusBadRequest, rec.Code)
+}
+
+func TestOpaDecision_OPADecisionError(t *testing.T) {
+ originalGetState := pdpstate.GetCurrentState
+ pdpstate.GetCurrentState = func() model.PdpState {
+ return model.Active
+ }
+ defer func() { pdpstate.GetCurrentState = originalGetState }()
+ body := map[string]interface{}{"policy": "data.policy"}
+ jsonBody, _ := json.Marshal(body)
+ req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(jsonBody))
+ rec := httptest.NewRecorder()
+
+ tmpFile, err := os.CreateTemp("", "config.json")
+ if err != nil {
+ t.Fatalf("Failed to create temp file: %v", err)
+ }
+ defer os.Remove(tmpFile.Name())
+
+ consts.OpasdkConfigPath = tmpFile.Name()
+
+ OpaDecision(rec, req)
+
+ assert.Equal(t, http.StatusBadRequest, rec.Code)
+}
+
+func TestOpaDecision_PassiveState(t *testing.T) {
+ originalGetState := pdpstate.GetCurrentState
+ pdpstate.GetCurrentState = func() model.PdpState {
+ return model.Passive
+ }
+ defer func() { pdpstate.GetCurrentState = originalGetState }()
+ req := httptest.NewRequest(http.MethodPost, "/opa/decision", nil)
+ rec := httptest.NewRecorder()
+
+ OpaDecision(rec, req)
+
+ assert.Equal(t, http.StatusInternalServerError, rec.Code)
+ assert.Contains(t, rec.Body.String(), " System Is In PASSIVE State")
+}
diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go
new file mode 100644
index 0000000..4c8a13b
--- /dev/null
+++ b/pkg/healthcheck/healthcheck.go
@@ -0,0 +1,74 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 healthcheck provides functionalities for handling health check requests.
+// This package includes a function to handle HTTP requests for health checks
+// and respond with the health status of the service.
+package healthcheck
+
+import (
+ "encoding/json"
+ "net/http"
+ "policy-opa-pdp/consts"
+ "policy-opa-pdp/pkg/log"
+ "policy-opa-pdp/pkg/model/oapicodegen"
+ "policy-opa-pdp/pkg/pdpattributes"
+ "policy-opa-pdp/pkg/utils"
+
+ "github.com/google/uuid"
+ openapi_types "github.com/oapi-codegen/runtime/types"
+)
+
+// handles HTTP requests for health checks and responds with the health status of the service.
+func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
+
+ requestId := r.Header.Get("X-ONAP-RequestID")
+ var parsedUUID *uuid.UUID
+ var healthCheckParams *oapicodegen.HealthcheckParams
+
+ if requestId != "" && utils.IsValidUUID(requestId) {
+ tempUUID, err := uuid.Parse(requestId)
+ if err != nil {
+ log.Warnf("Error Parsing the requestID: %v", err)
+ } else {
+ parsedUUID = &tempUUID
+ healthCheckParams = &oapicodegen.HealthcheckParams{
+ XONAPRequestID: (*openapi_types.UUID)(parsedUUID),
+ }
+ w.Header().Set("X-ONAP-RequestID", healthCheckParams.XONAPRequestID.String())
+ }
+ } else {
+ log.Warnf("Invalid or Missing Request ID")
+ requestId = "000000000000"
+ w.Header().Set("X-ONAP-RequestID", requestId)
+ }
+ w.Header().Set("X-LatestVersion", consts.LatestVersion)
+ w.Header().Set("X-PatchVersion", consts.PatchVersion)
+ w.Header().Set("X-MinorVersion", consts.MinorVersion)
+
+ response := &oapicodegen.HealthCheckReport{
+ Name: &pdpattributes.PdpName,
+ Url: &consts.OpaPdpUrl,
+ Healthy: &consts.HealtCheckStatus,
+ Code: &consts.OkCode,
+ Message: &consts.HealthCheckMessage,
+ }
+ log.Debug("Received Health Check message")
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(response)
+}
diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go
new file mode 100644
index 0000000..3e1876f
--- /dev/null
+++ b/pkg/healthcheck/healthcheck_test.go
@@ -0,0 +1,85 @@
+package healthcheck
+
+import (
+ "encoding/json"
+ "github.com/stretchr/testify/assert"
+ "net/http"
+ "net/http/httptest"
+ "policy-opa-pdp/pkg/model/oapicodegen"
+ "policy-opa-pdp/pkg/pdpattributes"
+ "testing"
+)
+
+// Success Test Case for HealthCheckHandler
+func TestHealthCheckHandler_Success(t *testing.T) {
+ // Prepare a request to the health check endpoint
+ req := httptest.NewRequest(http.MethodGet, "/healthcheck", nil)
+ w := httptest.NewRecorder()
+
+ // Call the HealthCheckHandler with the test request and response recorder
+ HealthCheckHandler(w, req)
+
+ // Check if the status code is OK (200)
+ assert.Equal(t, http.StatusOK, w.Code)
+
+ // Check if the response is a valid JSON and contains the expected fields
+ var response oapicodegen.HealthCheckReport
+ err := json.NewDecoder(w.Body).Decode(&response)
+ assert.NoError(t, err)
+ assert.Equal(t, pdpattributes.PdpName, *response.Name)
+ assert.Equal(t, "self", *response.Url)
+ assert.True(t, *response.Healthy)
+ assert.Equal(t,int32(200), *response.Code)
+ assert.Equal(t, "alive", *response.Message)
+}
+
+// Failure Test Case for HealthCheckHandler (Simulate failure by forcing an error)
+func TestHealthCheckHandler_Failure(t *testing.T) {
+ // Simulate an error by modifying the handler or the response
+ // For the sake of testing, we'll modify the handler to return a failure message
+ // You could also simulate a failure by forcing an error within the handler code itself
+ HealthCheckFailureHandler := func(w http.ResponseWriter, r *http.Request) {
+ // Modify response to simulate failure
+ response := oapicodegen.HealthCheckReport{
+ Name: strPtr("Unknown"),
+ Url: strPtr("self"),
+ Healthy: boolPtr(false),
+ Code: int32Ptr(500),
+ Message: strPtr("error"),
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusInternalServerError)
+ json.NewEncoder(w).Encode(response)
+ }
+
+ // Prepare a request to the health check endpoint
+ req := httptest.NewRequest(http.MethodGet, "/healthcheck", nil)
+ w := httptest.NewRecorder()
+
+ // Call the HealthCheckHandler with the test request and response recorder
+ HealthCheckFailureHandler(w, req)
+
+ // Check if the status code is InternalServerError (500)
+ assert.Equal(t, http.StatusInternalServerError, w.Code)
+
+ // Check if the response is a valid JSON and contains the expected failure fields
+ var response oapicodegen.HealthCheckReport
+ err := json.NewDecoder(w.Body).Decode(&response)
+ assert.NoError(t, err)
+ assert.False(t, *response.Healthy)
+ assert.Equal(t, int32(500), *response.Code)
+ assert.Equal(t, "error", *response.Message)
+
+}
+
+func strPtr(s string) *string {
+ return &s
+}
+
+func boolPtr(b bool) *bool {
+ return &b
+}
+
+func int32Ptr(i int32) *int32 {
+ return &i
+}
diff --git a/pkg/kafkacomm/handler/pdp_message_handler.go b/pkg/kafkacomm/handler/pdp_message_handler.go
new file mode 100644
index 0000000..8d7da92
--- /dev/null
+++ b/pkg/kafkacomm/handler/pdp_message_handler.go
@@ -0,0 +1,132 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// The handler package is responsible for processing messages from Kafka, specifically targeting the OPA
+// (Open Policy Agent) PDP (Policy Decision Point). It validates the message type,
+//
+// ensures it is relevant to the current PDP, and dispatches the message for appropriate processing.
+package handler
+
+import (
+ "encoding/json"
+ "policy-opa-pdp/consts"
+ "policy-opa-pdp/pkg/kafkacomm"
+ "policy-opa-pdp/pkg/kafkacomm/publisher"
+ "policy-opa-pdp/pkg/log"
+ "policy-opa-pdp/pkg/pdpattributes"
+)
+
+type OpaPdpMessage struct {
+ Name string `json:"name"` // Name of the PDP (optional for broadcast messages).
+ MessageType string `json:"MessageName"` // Type of the message (e.g., PDP_UPDATE, PDP_STATE_CHANGE, etc.)
+ PdpGroup string `json:"pdpGroup"` // Group to which the PDP belongs.
+ PdpSubgroup string `json:"pdpSubgroup"` // Subgroup within the PDP group.
+}
+
+// Checks if the incoming Kafka message belongs to the current PDP instance.
+func checkIfMessageIsForOpaPdp(message OpaPdpMessage) bool {
+
+ if message.Name != "" {
+ // message included a PDP name, check if matches
+ //log.Infof(" Message Name is not empty")
+ return message.Name == pdpattributes.PdpName
+ }
+
+ // message does not provide a PDP name - must be a broadcast
+ if message.PdpGroup == "" {
+ //log.Infof(" Message PDP Group is empty")
+ return false
+ }
+
+ if pdpattributes.PdpSubgroup == "" {
+ // this PDP has no assignment yet, thus should ignore broadcast messages
+ //log.Infof(" pdpstate PDP subgroup is empty")
+ return false
+ }
+
+ if message.PdpGroup != consts.PdpGroup {
+ //log.Infof(" message pdp group is not equal to cons pdp group")
+ return false
+ }
+
+ if message.PdpSubgroup == "" {
+ //message was broadcast to entire group
+ //log.Infof(" message pdp subgroup is empty")
+ return true
+ }
+
+ return message.PdpSubgroup == pdpattributes.PdpSubgroup
+}
+
+// Handles incoming Kafka messages, validates their relevance to the current PDP,
+// and dispatches them for further processing based on their type.
+func PdpMessageHandler(kc *kafkacomm.KafkaConsumer, topic string, p publisher.PdpStatusSender) error {
+
+ log.Debug("Starting PDP Message Listener.....")
+ var stopConsuming bool
+ for !stopConsuming {
+ message, err := kafkacomm.ReadKafkaMessages(kc)
+ if err != nil {
+ log.Warnf("Failed to Read Kafka Messages: %v\n", err)
+ continue
+ }
+ log.Debugf("[IN|KAFKA|%s]\n%s", topic, string(message))
+
+ if message != nil {
+
+ var opaPdpMessage OpaPdpMessage
+
+ err = json.Unmarshal(message, &opaPdpMessage)
+ if err != nil {
+ log.Warnf("Failed to UnMarshal Messages: %v\n", err)
+ continue
+ }
+
+ if !checkIfMessageIsForOpaPdp(opaPdpMessage) {
+
+ log.Warnf("Not a valid Opa Pdp Message")
+ continue
+ }
+
+ switch opaPdpMessage.MessageType {
+
+ case "PDP_UPDATE":
+ err = PdpUpdateMessageHandler(message, p)
+ if err != nil {
+ log.Warnf("Error processing Update Message: %v", err)
+ }
+
+ case "PDP_STATE_CHANGE":
+ err = PdpStateChangeMessageHandler(message, p)
+ if err != nil {
+ log.Warnf("Error processing Update Message: %v", err)
+ }
+
+ case "PDP_STATUS":
+ log.Debugf("discarding event of type PDP_STATUS")
+ continue
+ default:
+ log.Errorf("This is not a valid Message Type: %s", opaPdpMessage.MessageType)
+ continue
+
+ }
+
+ }
+ }
+ return nil
+
+}
diff --git a/pkg/kafkacomm/handler/pdp_message_handler_test.go b/pkg/kafkacomm/handler/pdp_message_handler_test.go
new file mode 100644
index 0000000..3764c9e
--- /dev/null
+++ b/pkg/kafkacomm/handler/pdp_message_handler_test.go
@@ -0,0 +1,142 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 handler
+
+import (
+ "github.com/stretchr/testify/assert"
+ "policy-opa-pdp/pkg/pdpattributes"
+ "testing"
+)
+
+/*
+checkIfMessageIsForOpaPdp_Check
+Description: Validating Message Attributes
+Input: PDP message
+Expected Output: Returning true stating all the values are validated successfully
+*/
+func TestCheckIfMessageIsForOpaPdp_Check(t *testing.T) {
+
+ var opapdpMessage OpaPdpMessage
+
+ opapdpMessage.Name = "opa-3a318049-813f-4172-b4d3-7d4f466e5b80"
+ opapdpMessage.MessageType = "PDP_STATUS"
+ opapdpMessage.PdpGroup = "defaultGroup"
+ opapdpMessage.PdpSubgroup = "opa"
+
+ assert.False(t, checkIfMessageIsForOpaPdp(opapdpMessage), "Its a valid Opa Pdp Message")
+
+}
+
+/*
+checkIfMessageIsForOpaPdp_Check_Message_Name
+Description: Validating Message Attributes
+Input: PDP message with name as empty
+Expected Output: Returning Error since it is not valid message
+*/
+func TestCheckIfMessageIsForOpaPdp_Check_Message_Name(t *testing.T) {
+
+ var opapdpMessage OpaPdpMessage
+
+ opapdpMessage.Name = ""
+ opapdpMessage.MessageType = "PDP_STATUS"
+ opapdpMessage.PdpGroup = "defaultGroup"
+ opapdpMessage.PdpSubgroup = "opa"
+
+ assert.False(t, checkIfMessageIsForOpaPdp(opapdpMessage), "Not a valid Opa Pdp Message")
+
+}
+
+/*
+checkIfMessageIsForOpaPdp_Check_PdpGroup
+Description: Validating Message Attributes
+Input: PDP message with invalid PdpGroup
+Expected Output: Returning Error since it is not valid message
+*/
+func TestCheckIfMessageIsForOpaPdp_Check_PdpGroup(t *testing.T) {
+
+ var opapdpMessage OpaPdpMessage
+
+ opapdpMessage.Name = ""
+ opapdpMessage.MessageType = "PDP_STATUS"
+ opapdpMessage.PdpGroup = "defaultGroup"
+ opapdpMessage.PdpSubgroup = "opa"
+
+ pdpattributes.PdpSubgroup = "opa"
+ assert.True(t, checkIfMessageIsForOpaPdp(opapdpMessage), "Its a valid Opa Pdp Message")
+
+}
+
+/*
+checkIfMessageIsForOpaPdp_Check_EmptyPdpGroup
+Description: Validating Message Attributes
+Input: PDP Group Empty
+Expected Output: Returning Error since it is not valid message
+*/
+func TestCheckIfMessageIsForOpaPdp_Check_EmptyPdpGroup(t *testing.T) {
+
+ var opapdpMessage OpaPdpMessage
+
+ opapdpMessage.Name = ""
+ opapdpMessage.MessageType = "PDP_STATUS"
+ opapdpMessage.PdpGroup = ""
+ opapdpMessage.PdpSubgroup = "opa"
+
+ assert.False(t, checkIfMessageIsForOpaPdp(opapdpMessage), "Not a valid Opa Pdp Message")
+
+}
+
+/*
+checkIfMessageIsForOpaPdp_Check_PdpSubgroup
+Description: Validating Message Attributes
+Input: PDP message with invalid PdpSubgroup
+Expected Output: Returning Error since it is not valid message
+*/
+func TestCheckIfMessageIsForOpaPdp_Check_PdpSubgroup(t *testing.T) {
+
+ var opapdpMessage OpaPdpMessage
+
+ opapdpMessage.Name = ""
+ opapdpMessage.MessageType = "PDP_STATUS"
+ opapdpMessage.PdpGroup = "defaultGroup"
+ opapdpMessage.PdpSubgroup = "opa"
+
+ pdpattributes.PdpSubgroup = "opa"
+ assert.True(t, checkIfMessageIsForOpaPdp(opapdpMessage), "It's a valid Opa Pdp Message")
+
+}
+
+/*
+checkIfMessageIsForOpaPdp_Check_IncorrectPdpSubgroup
+Description: Validating Message Attributes
+Input: PDP message with empty PdpSubgroup
+Expected Output: Returning Error since it is not valid message
+*/
+func TestCheckIfMessageIsForOpaPdp_Check_IncorrectPdpSubgroup(t *testing.T) {
+
+ var opapdpMessage OpaPdpMessage
+
+ opapdpMessage.Name = ""
+ opapdpMessage.MessageType = "PDP_STATUS"
+ opapdpMessage.PdpGroup = "defaultGroup"
+ opapdpMessage.PdpSubgroup = "o"
+
+ pdpattributes.PdpSubgroup = "opa"
+ assert.False(t, checkIfMessageIsForOpaPdp(opapdpMessage), "Not a valid Opa Pdp Message")
+
+}
diff --git a/pkg/kafkacomm/handler/pdp_state_change_handler.go b/pkg/kafkacomm/handler/pdp_state_change_handler.go
new file mode 100644
index 0000000..32d998f
--- /dev/null
+++ b/pkg/kafkacomm/handler/pdp_state_change_handler.go
@@ -0,0 +1,57 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// will process the state change message from pap and send the pdp status response.
+package handler
+
+import (
+ "encoding/json"
+ "policy-opa-pdp/pkg/kafkacomm/publisher"
+ "policy-opa-pdp/pkg/log"
+ "policy-opa-pdp/pkg/model"
+ "policy-opa-pdp/pkg/pdpstate"
+)
+
+// Processes incoming messages indicating a PDP state change.
+// This includes updating the PDP state and sending a status response when the state transitions.
+func PdpStateChangeMessageHandler(message []byte, p publisher.PdpStatusSender) error {
+
+ var pdpStateChange model.PdpStateChange
+
+ err := json.Unmarshal(message, &pdpStateChange)
+ if err != nil {
+ log.Debugf("Failed to UnMarshal Messages: %v\n", err)
+ return err
+ }
+
+ log.Debugf("PDP STATE CHANGE message received: %s", string(message))
+
+ if pdpStateChange.State != "" {
+ pdpstate.SetState(pdpStateChange.State)
+
+ }
+
+ log.Debugf("State change from PASSIVE To : %s", pdpstate.GetState())
+ err = publisher.SendStateChangeResponse(p, &pdpStateChange)
+ if err != nil {
+ log.Debugf("Failed to Send State Change Response Message: %v\n", err)
+ return err
+ }
+ log.Infof("PDP_STATUS With State Change Message Sent Successfully")
+
+ return nil
+}
diff --git a/pkg/kafkacomm/handler/pdp_state_change_handler_test.go b/pkg/kafkacomm/handler/pdp_state_change_handler_test.go
new file mode 100644
index 0000000..f7e8f84
--- /dev/null
+++ b/pkg/kafkacomm/handler/pdp_state_change_handler_test.go
@@ -0,0 +1,93 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 handler
+
+import (
+ "policy-opa-pdp/pkg/kafkacomm/publisher"
+ "policy-opa-pdp/pkg/model"
+ "policy-opa-pdp/pkg/pdpstate"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+)
+
+// MockPdpStatusSender is a mock implementation of the PdpStatusSender interface
+type MockPdpStatusSender struct {
+ mock.Mock
+}
+
+func (m *MockPdpStatusSender) SendStateChangeResponse(p *publisher.PdpStatusSender, pdpStateChange *model.PdpStateChange) error {
+ args := m.Called(p, pdpStateChange)
+ return args.Error(0)
+}
+
+func (m *MockPdpStatusSender) SendPdpStatus(status model.PdpStatus) error {
+ args := m.Called(status)
+ return args.Error(0)
+}
+
+func TestPdpStateChangeMessageHandler(t *testing.T) {
+
+ // Create a mock PdpStatusSender
+ mockSender := new(MockPdpStatusSender)
+
+ // Define test cases
+ tests := []struct {
+ name string
+ message []byte
+ expectedState string
+ mockError error
+ expectError bool
+ }{
+ {
+ name: "Valid state change",
+ message: []byte(`{"state":"ACTIVE"}`),
+ expectedState: "ACTIVE",
+ mockError: nil,
+ expectError: false,
+ },
+ {
+ name: "Invalid JSON",
+ message: []byte(`{"state":}`),
+ mockError: nil,
+ expectError: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Set up the mock to return the expected error
+ mockSender.On("SendStateChangeResponse", mock.Anything, mock.Anything).Return(tt.mockError)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(nil)
+
+ // Call the handler
+ err := PdpStateChangeMessageHandler(tt.message, mockSender)
+
+ // Check the results
+ if tt.expectError {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, tt.expectedState, pdpstate.GetState().String())
+ }
+
+ })
+ }
+}
diff --git a/pkg/kafkacomm/handler/pdp_update_message_handler.go b/pkg/kafkacomm/handler/pdp_update_message_handler.go
new file mode 100644
index 0000000..632bcc8
--- /dev/null
+++ b/pkg/kafkacomm/handler/pdp_update_message_handler.go
@@ -0,0 +1,64 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// will process the update message from pap and send the pdp status response.
+package handler
+
+import (
+ "encoding/json"
+ "github.com/go-playground/validator/v10"
+ "policy-opa-pdp/pkg/kafkacomm/publisher"
+ "policy-opa-pdp/pkg/log"
+ "policy-opa-pdp/pkg/model"
+ "policy-opa-pdp/pkg/pdpattributes"
+)
+
+// Handles messages of type PDP_UPDATE sent from the Policy Administration Point (PAP).
+// It validates the incoming data, updates PDP attributes, and sends a response back to the sender.
+func PdpUpdateMessageHandler(message []byte, p publisher.PdpStatusSender) error {
+
+ var pdpUpdate model.PdpUpdate
+ err := json.Unmarshal(message, &pdpUpdate)
+ if err != nil {
+ log.Debugf("Failed to UnMarshal Messages: %v\n", err)
+ return err
+ }
+ //Initialize Validator and validate Struct after unmarshalling
+ validate := validator.New()
+
+ err = validate.Struct(pdpUpdate)
+ if err != nil {
+ for _, err := range err.(validator.ValidationErrors) {
+ log.Infof("Field %s failed on the %s tag\n", err.Field(), err.Tag())
+ }
+ return err
+ }
+
+ log.Debugf("PDP_UPDATE Message received: %s", string(message))
+
+ pdpattributes.SetPdpSubgroup(pdpUpdate.PdpSubgroup)
+ pdpattributes.SetPdpHeartbeatInterval(pdpUpdate.PdpHeartbeatIntervalMs)
+
+ err = publisher.SendPdpUpdateResponse(p, &pdpUpdate)
+ if err != nil {
+ log.Debugf("Failed to Send Update Response Message: %v\n", err)
+ return err
+ }
+ log.Infof("PDP_STATUS Message Sent Successfully")
+ go publisher.StartHeartbeatIntervalTimer(pdpattributes.PdpHeartbeatInterval, p)
+ return nil
+}
diff --git a/pkg/kafkacomm/handler/pdp_update_message_handler_test.go b/pkg/kafkacomm/handler/pdp_update_message_handler_test.go
new file mode 100644
index 0000000..061f1ce
--- /dev/null
+++ b/pkg/kafkacomm/handler/pdp_update_message_handler_test.go
@@ -0,0 +1,196 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 handler
+
+import (
+ "errors"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+ "policy-opa-pdp/pkg/kafkacomm/publisher/mocks"
+ "testing"
+)
+
+/*
+PdpUpdateMessageHandler_success
+Description: Test by sending a valid input message for pdp update
+Input: valid input
+Expected Output: PDP Update Message should be sent sucessfully.
+*/
+func TestPdpUpdateMessageHandler_Success(t *testing.T) {
+
+ messageString := `{
+ "source":"pap-c17b4dbc-3278-483a-ace9-98f3157245c0",
+ "pdpHeartbeatIntervalMs":120000,
+ "policiesToBeDeployed":[],
+ "policiesToBeUndeployed":[],
+ "messageName":"PDP_UPDATE",
+ "requestId":"41c117db-49a0-40b0-8586-5580d042d0a1",
+ "timestampMs":1730722305297,
+ "name":"opa-21cabb3e-f652-4ca6-b498-a77e62fcd059",
+ "pdpGroup":"defaultGroup",
+ "pdpSubgroup":"opa"
+ }`
+
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(nil)
+
+ err := PdpUpdateMessageHandler([]byte(messageString), mockSender)
+ assert.NoError(t, err)
+
+}
+
+/*
+PdpUpdateMessageHandler_Message_Unmarshal_Failure1
+Description: Test by sending a invalid input message which should result in a Json unmarhsal error
+Input: invalid input Message by renaming params or removing certain params
+Expected Output: Message Handler should exit gracefully stating the error.
+*/
+func TestPdpUpdateMessageHandler_Message_Unmarshal_Failure1(t *testing.T) {
+
+ // sending only source parameter in the message string
+ messageString := `{
+ "source":"pap-c17b4dbc-3278-483a-ace9-98f3157245c0"}`
+
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(errors.New("Jsonunmarshal Error"))
+
+ err := PdpUpdateMessageHandler([]byte(messageString), mockSender)
+ assert.Error(t, err)
+
+}
+
+/*
+PdpUpdateMessageHandler_Message_Unmarshal_Failure2
+Description: Test by sending a invalid input message which should result in a Json unmarhsal error
+Input: invalid input Message by renaming params or removing certain params
+Expected Output: Message Handler should exit gracefully stating the error.
+*/
+func TestPdpUpdateMessageHandler_Message_Unmarshal_Failure2(t *testing.T) {
+
+ // invlaid params by mispelling a param "source"
+
+ messageString := `{
+ "soce":"pap-c17b4dbc-3278-483a-ace9-98f3157245c0",
+ "pdpHeartbeatIntervalMs":120000}`
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(errors.New("Jsonunmarshal Error"))
+
+ err := PdpUpdateMessageHandler([]byte(messageString), mockSender)
+ assert.Error(t, err)
+
+}
+
+/*
+PdpUpdateMessageHandler_Message_Unmarshal_Failure3
+Description: Test by sending a invalid input message which should result in a Json unmarhsal error
+Input: {}
+Expected Output: Message Handler should exit gracefully stating the error.
+*/
+func TestPdpUpdateMessageHandler_Message_Unmarshal_Failure3(t *testing.T) {
+
+ // invlaid params by mispelling a param "source"
+
+ messageString := `{
+ "soce:"pap-c17b4dbc-3278-483a-ace9-98f3157245c0",
+ "pdpHeartbeatIntervalMs":120000}`
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(errors.New("Jsonunmarshal Error"))
+
+ err := PdpUpdateMessageHandler([]byte(messageString), mockSender)
+ assert.Error(t, err)
+
+}
+
+/*
+PdpUpdateMessageHandler_Message_Unmarshal_Failure4
+Description: Test by sending a invalid input message which should result in a Json unmarhsal error
+Input: empty
+Expected Output: Message Handler should exit gracefully stating the error.
+*/
+func TestPdpUpdateMessageHandler_Message_Unmarshal_Failure4(t *testing.T) {
+
+ // invlaid params by mispelling a param "source"
+
+ messageString := `""`
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(errors.New("Jsonunmarshal Error"))
+
+ err := PdpUpdateMessageHandler([]byte(messageString), mockSender)
+ assert.Error(t, err)
+
+}
+
+/*
+PdpUpdateMessageHandler_Fails_Sending_PdpUpdateResponse
+Description: Test by sending a invalid attribute for pdpstate which should result in a failure in sending pdp update response
+Input: invalid input config set for pdpstate
+Expected Output: Message Handler should exit gracefully stating the error.
+*/
+func TestPdpUpdateMessageHandler_Fails_Sending_UpdateResponse(t *testing.T) {
+
+ // invalid value set to pdpSubgroup -->empty ""
+ messageString := `{
+ "source":"pap-c17b4dbc-3278-483a-ace9-98f3157245c0",
+ "pdpHeartbeatIntervalMs":120000,
+ "policiesToBeDeployed":[],
+ "policiesToBeUndeployed":[],
+ "messageName":"PDP_UPDATE",
+ "requestId":"41c117db-49a0-40b0-8586-5580d042d0a1",
+ "timestampMs":1730722305297,
+ "name":"opa-21cabb3e-f652-4ca6-b498-a77e62fcd059",
+ "pdpGroup":"defaultGroup"
+ }`
+
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(errors.New("Error in Sending PDP Update Response"))
+
+ err := PdpUpdateMessageHandler([]byte(messageString), mockSender)
+ assert.Error(t, err)
+
+}
+
+/*
+PdpUpdateMessageHandler_Invalid_Starttimeinterval
+Description: Test by sending a invalid time value attribute for pdpstate which should result in a failure in starting heartbeat interval
+Input: invalid input message for pdpstate heartbeat interval
+Expected Output: Message Handler should exit gracefully stating the error.
+*/
+func TestPdpUpdateMessageHandler_Invalid_Starttimeinterval(t *testing.T) {
+
+ //invalid interval set to negative -1000
+ messageString := `{
+ "source":"pap-c17b4dbc-3278-483a-ace9-98f3157245c0",
+ "pdpHeartbeatIntervalMs":-1000,
+ "policiesToBeDeployed":[],
+ "policiesToBeUndeployed":[],
+ "messageName":"PDP_UPDATE",
+ "requestId":"41c117db-49a0-40b0-8586-5580d042d0a1",
+ "timestampMs":1730722305297,
+ "name":"opa-21cabb3e-f652-4ca6-b498-a77e62fcd059",
+ "pdpGroup":"defaultGroup",
+ "pdpSubgroup":"opa"
+ }`
+
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(errors.New("Invalid Interval Time for Heartbeat"))
+
+ err := PdpUpdateMessageHandler([]byte(messageString), mockSender)
+ assert.Error(t, err)
+
+}
diff --git a/pkg/kafkacomm/mocks/kafkaconsumerinterface.go b/pkg/kafkacomm/mocks/kafkaconsumerinterface.go
new file mode 100644
index 0000000..ca5140e
--- /dev/null
+++ b/pkg/kafkacomm/mocks/kafkaconsumerinterface.go
@@ -0,0 +1,96 @@
+// Code generated by mockery v2.46.3. DO NOT EDIT.
+
+package mocks
+
+import (
+ kafka "github.com/confluentinc/confluent-kafka-go/kafka"
+
+ mock "github.com/stretchr/testify/mock"
+
+ time "time"
+)
+
+// KafkaConsumerInterface is an autogenerated mock type for the KafkaConsumerInterface type
+type KafkaConsumerInterface struct {
+ mock.Mock
+}
+
+// Close provides a mock function with given fields:
+func (_m *KafkaConsumerInterface) Close() error {
+ ret := _m.Called()
+
+ if len(ret) == 0 {
+ panic("no return value specified for Close")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func() error); ok {
+ r0 = rf()
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// ReadMessage provides a mock function with given fields: timeout
+func (_m *KafkaConsumerInterface) ReadMessage(timeout time.Duration) (*kafka.Message, error) {
+ ret := _m.Called(timeout)
+
+ if len(ret) == 0 {
+ panic("no return value specified for ReadMessage")
+ }
+
+ var r0 *kafka.Message
+ var r1 error
+ if rf, ok := ret.Get(0).(func(time.Duration) (*kafka.Message, error)); ok {
+ return rf(timeout)
+ }
+ if rf, ok := ret.Get(0).(func(time.Duration) *kafka.Message); ok {
+ r0 = rf(timeout)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*kafka.Message)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(time.Duration) error); ok {
+ r1 = rf(timeout)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// Unsubscribe provides a mock function with given fields:
+func (_m *KafkaConsumerInterface) Unsubscribe() error {
+ ret := _m.Called()
+
+ if len(ret) == 0 {
+ panic("no return value specified for Unsubscribe")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func() error); ok {
+ r0 = rf()
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// NewKafkaConsumerInterface creates a new instance of KafkaConsumerInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewKafkaConsumerInterface(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *KafkaConsumerInterface {
+ mock := &KafkaConsumerInterface{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/pkg/kafkacomm/mocks/kafkaproducerinterface.go b/pkg/kafkacomm/mocks/kafkaproducerinterface.go
new file mode 100644
index 0000000..97b6f53
--- /dev/null
+++ b/pkg/kafkacomm/mocks/kafkaproducerinterface.go
@@ -0,0 +1,51 @@
+// Code generated by mockery v2.46.3. DO NOT EDIT.
+
+package mocks
+
+import (
+ kafka "github.com/confluentinc/confluent-kafka-go/kafka"
+
+ mock "github.com/stretchr/testify/mock"
+)
+
+// KafkaProducerInterface is an autogenerated mock type for the KafkaProducerInterface type
+type KafkaProducerInterface struct {
+ mock.Mock
+}
+
+// Close provides a mock function with given fields:
+func (_m *KafkaProducerInterface) Close() {
+ _m.Called()
+}
+
+// Produce provides a mock function with given fields: _a0, _a1
+func (_m *KafkaProducerInterface) Produce(_a0 *kafka.Message, _a1 chan kafka.Event) error {
+ ret := _m.Called(_a0, _a1)
+
+ if len(ret) == 0 {
+ panic("no return value specified for Produce")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(*kafka.Message, chan kafka.Event) error); ok {
+ r0 = rf(_a0, _a1)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// NewKafkaProducerInterface creates a new instance of KafkaProducerInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewKafkaProducerInterface(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *KafkaProducerInterface {
+ mock := &KafkaProducerInterface{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/pkg/kafkacomm/pdp_topic_consumer.go b/pkg/kafkacomm/pdp_topic_consumer.go
new file mode 100644
index 0000000..4858bdf
--- /dev/null
+++ b/pkg/kafkacomm/pdp_topic_consumer.go
@@ -0,0 +1,103 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// kafkacomm package provides a structured way to create and manage Kafka consumers,
+// handle subscriptions, and read messages from Kafka topics
+package kafkacomm
+
+import (
+ "github.com/confluentinc/confluent-kafka-go/kafka"
+ "policy-opa-pdp/cfg"
+ "policy-opa-pdp/pkg/log"
+ "time"
+)
+
+// KafkaConsumerInterface defines the interface for a Kafka consumer.
+type KafkaConsumerInterface interface {
+ Close() error
+ Unsubscribe() error
+ ReadMessage(timeout time.Duration) (*kafka.Message, error)
+}
+
+// KafkaConsumer is a wrapper around the Kafka consumer.
+type KafkaConsumer struct {
+ Consumer KafkaConsumerInterface
+}
+
+// Close closes the KafkaConsumer
+func (kc *KafkaConsumer) Close() {
+ kc.Consumer.Close()
+}
+
+// Unsubscribe unsubscribes the KafkaConsumer
+func (kc *KafkaConsumer) Unsubscribe() error {
+ if err := kc.Consumer.Unsubscribe(); err != nil {
+ log.Warnf("Error Unsubscribing :%v", err)
+ return err
+ }
+ log.Debug("Unsubscribe From Topic")
+ return nil
+}
+
+// creates a new Kafka consumer and returns it
+func NewKafkaConsumer() (*KafkaConsumer, error) {
+ brokers := cfg.BootstrapServer
+ groupid := cfg.GroupId
+ topic := cfg.Topic
+ useSASL := cfg.UseSASLForKAFKA
+ username := cfg.KAFKA_USERNAME
+ password := cfg.KAFKA_PASSWORD
+
+ // Add Kafka Connection Properties ....
+ configMap := &kafka.ConfigMap{
+ "bootstrap.servers": brokers,
+ "group.id": groupid,
+ "auto.offset.reset": "earliest",
+ }
+ //for STRIMZI-KAFKA in case sasl is enabled
+ if useSASL == "true" {
+ configMap.SetKey("sasl.mechanism", "SCRAM-SHA-512")
+ configMap.SetKey("sasl.username", username)
+ configMap.SetKey("sasl.password", password)
+ configMap.SetKey("security.protocol", "SASL_PLAINTEXT")
+ }
+
+ // create new Kafka Consumer
+ consumer, err := kafka.NewConsumer(configMap)
+ if err != nil {
+ log.Warnf("Error creating consumer: %v\n", err)
+ return nil, err
+ }
+ //subscribe to topic
+ err = consumer.SubscribeTopics([]string{topic}, nil)
+ if err != nil {
+ log.Warnf("Error subcribing to topic: %v\n", err)
+ return nil, err
+ }
+ log.Debugf("Topic Subscribed... : %v", topic)
+ return &KafkaConsumer{Consumer: consumer}, nil
+}
+
+// gets the Kafka messages on the subscribed topic
+func ReadKafkaMessages(kc *KafkaConsumer) ([]byte, error) {
+ msg, err := kc.Consumer.ReadMessage(-1)
+ if err != nil {
+ log.Warnf("Error reading Kafka message: %v", err)
+ return nil, err
+ }
+ return msg.Value, nil
+}
diff --git a/pkg/kafkacomm/pdp_topic_consumer_test.go b/pkg/kafkacomm/pdp_topic_consumer_test.go
new file mode 100644
index 0000000..2fdfa90
--- /dev/null
+++ b/pkg/kafkacomm/pdp_topic_consumer_test.go
@@ -0,0 +1,129 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 kafkacomm
+
+import (
+ "errors"
+ "policy-opa-pdp/pkg/kafkacomm/mocks"
+ "testing"
+
+ "github.com/confluentinc/confluent-kafka-go/kafka"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+)
+
+func TestNewKafkaConsumer(t *testing.T) {
+ // Assuming configuration is correctly loaded from cfg package
+ // You can mock or override cfg values here if needed
+
+ consumer, err := NewKafkaConsumer()
+ assert.NoError(t, err, "Expected no error when creating Kafka consumer")
+ assert.NotNil(t, consumer, "Expected a non-nil KafkaConsumer")
+
+ // Clean up
+ if consumer != nil {
+ consumer.Close()
+ }
+}
+
+func TestReadKafkaMessages_Success(t *testing.T) {
+ // Create a new mock for ConsumerInterface
+ mockConsumer := new(mocks.KafkaConsumerInterface)
+
+ // Create a KafkaConsumer with the mock
+ kc := &KafkaConsumer{Consumer: mockConsumer}
+
+ // Define the expected message
+ expectedMsg := &kafka.Message{Value: []byte("test message")}
+
+ // Set up the mock to return the expected message
+ mockConsumer.On("ReadMessage", mock.Anything).Return(expectedMsg, nil)
+
+ // Test ReadKafkaMessages
+ msg, err := ReadKafkaMessages(kc)
+ assert.NoError(t, err, "Expected no error when reading message")
+ assert.Equal(t, expectedMsg.Value, msg, "Expected message content to match")
+
+ // Assert expectations
+ mockConsumer.AssertExpectations(t)
+}
+
+func TestReadKafkaMessages_Error(t *testing.T) {
+ mockConsumer := new(mocks.KafkaConsumerInterface)
+
+ kc := &KafkaConsumer{Consumer: mockConsumer}
+
+ // Set up the mock to return an error
+ expectedErr := errors.New("read error")
+ mockConsumer.On("ReadMessage", mock.Anything).Return(nil, expectedErr)
+
+ msg, err := ReadKafkaMessages(kc)
+ assert.Error(t, err, "Expected an error when reading message")
+ assert.Nil(t, msg, "Expected message to be nil on error")
+
+ mockConsumer.AssertExpectations(t)
+}
+
+func TestKafkaConsumer_Close(t *testing.T) {
+ mockConsumer := new(mocks.KafkaConsumerInterface)
+
+ kc := &KafkaConsumer{Consumer: mockConsumer}
+
+ // Set up the mock for Close
+ mockConsumer.On("Close").Return(nil)
+
+ // Test Close method
+ kc.Close()
+
+ // Verify that Close was called
+ mockConsumer.AssertExpectations(t)
+}
+
+func TestKafkaConsumer_Unsubscribe(t *testing.T) {
+ mockConsumer := new(mocks.KafkaConsumerInterface)
+
+ kc := &KafkaConsumer{Consumer: mockConsumer}
+
+ // Set up the mock for Unsubscribe
+ mockConsumer.On("Unsubscribe").Return(nil)
+
+ // Test Unsubscribe method
+ err := kc.Unsubscribe()
+ assert.NoError(t, err)
+
+ // Verify that Unsubscribe was called
+ mockConsumer.AssertExpectations(t)
+}
+
+func TestKafkaConsumer_Unsubscribe_Error(t *testing.T) {
+ mockConsumer := new(mocks.KafkaConsumerInterface)
+ mockError := errors.New("Unsubscribe error")
+ kc := &KafkaConsumer{Consumer: mockConsumer}
+
+ // Set up the mock for Unsubscribe
+ mockConsumer.On("Unsubscribe").Return(mockError)
+
+ // Test Unsubscribe method
+ err := kc.Unsubscribe()
+ assert.Error(t, err)
+ assert.Equal(t, mockError, err)
+
+ // Verify that Unsubscribe was called
+ mockConsumer.AssertExpectations(t)
+}
diff --git a/pkg/kafkacomm/pdp_topic_producer.go b/pkg/kafkacomm/pdp_topic_producer.go
new file mode 100644
index 0000000..1b11b35
--- /dev/null
+++ b/pkg/kafkacomm/pdp_topic_producer.go
@@ -0,0 +1,107 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 kafkacomm provides utilities for producing messages to a Kafka topic
+// using a configurable Kafka producer. It supports SASL authentication and
+// dynamic topic configuration.
+package kafkacomm
+
+import (
+ "github.com/confluentinc/confluent-kafka-go/kafka"
+ "policy-opa-pdp/cfg"
+ "sync"
+ "log"
+)
+
+type KafkaProducerInterface interface {
+ Produce(*kafka.Message, chan kafka.Event) error
+ Close()
+}
+
+// KafkaProducer wraps a Kafka producer instance and a topic to provide
+// a simple interface for producing messages.
+type KafkaProducer struct {
+ producer KafkaProducerInterface
+ topic string
+}
+
+var (
+ instance *KafkaProducer
+ once sync.Once
+)
+
+// GetKafkaProducer initializes and returns a KafkaProducer instance which is a singleton.
+// It configures the Kafka producer with the given bootstrap servers and topic.
+// If SASL authentication is enabled via the configuration, the necessary credentials
+// are set in the producer configuration.
+func GetKafkaProducer(bootstrapServers, topic string) (*KafkaProducer, error) {
+ var err error
+ once.Do(func() {
+ brokers := cfg.BootstrapServer
+ useSASL := cfg.UseSASLForKAFKA
+ username := cfg.KAFKA_USERNAME
+ password := cfg.KAFKA_PASSWORD
+
+ // Add Kafka Connection Properties ....
+ configMap := &kafka.ConfigMap{
+ "bootstrap.servers": brokers,
+ }
+
+ if useSASL == "true" {
+ configMap.SetKey("sasl.mechanism", "SCRAM-SHA-512")
+ configMap.SetKey("sasl.username", username)
+ configMap.SetKey("sasl.password", password)
+ configMap.SetKey("security.protocol", "SASL_PLAINTEXT")
+ }
+
+ p, err := kafka.NewProducer(configMap)
+ if err != nil {
+ return
+ }
+ instance = &KafkaProducer{
+ producer: p,
+ topic: topic,
+ }
+
+ })
+ return instance, err
+}
+
+// Produce sends a message to the configured Kafka topic.
+// It takes the message payload as a byte slice and returns any errors
+func (kp *KafkaProducer) Produce(message []byte) error {
+ kafkaMessage := &kafka.Message{
+ TopicPartition: kafka.TopicPartition{Topic: &kp.topic, Partition: kafka.PartitionAny},
+ Value: []byte(message),
+ }
+ err := kp.producer.Produce(kafkaMessage, nil)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// Close shuts down the Kafka producer, releasing all resources.
+func (kp *KafkaProducer) Close() {
+
+ if kp == nil || kp.producer == nil {
+ log.Println("KafkaProducer or producer is nil, skipping Close.")
+ return
+ }
+ kp.producer.Close()
+ log.Println("KafkaProducer closed successfully.")
+}
diff --git a/pkg/kafkacomm/pdp_topic_producer_test.go b/pkg/kafkacomm/pdp_topic_producer_test.go
new file mode 100644
index 0000000..55f3bc8
--- /dev/null
+++ b/pkg/kafkacomm/pdp_topic_producer_test.go
@@ -0,0 +1,117 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 kafkacomm
+
+import (
+ "errors"
+ "testing"
+ "time"
+ // "github.com/confluentinc/confluent-kafka-go/kafka"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+
+ "policy-opa-pdp/pkg/kafkacomm/mocks" // Adjust to your actual mock path
+)
+
+func TestKafkaProducer_Produce_Success(t *testing.T) {
+ done := make(chan struct{})
+
+ go func() {
+ defer close(done)
+ // Arrange
+ mockProducer := new(mocks.KafkaProducerInterface)
+ topic := "test-topic"
+ kp := &KafkaProducer{
+ producer: mockProducer,
+ topic: topic,
+ }
+
+ message := []byte("test message")
+
+ // Mock Produce method to simulate successful delivery
+ mockProducer.On("Produce", mock.Anything, mock.Anything).Return(nil)
+
+ // Act
+ err := kp.Produce(message)
+
+ assert.NoError(t, err)
+ mockProducer.AssertExpectations(t)
+ }()
+ select {
+ case <-done:
+ case <-time.After(10 * time.Second):
+ t.Fatal("test timed out")
+ }
+
+}
+
+func TestKafkaProducer_Produce_Error(t *testing.T) {
+ // Arrange
+ mockProducer := new(mocks.KafkaProducerInterface)
+ topic := "test-topic"
+ kp := &KafkaProducer{
+ producer: mockProducer,
+ topic: topic,
+ }
+
+ // Simulate production error
+ mockProducer.On("Produce", mock.Anything, mock.Anything).Return(errors.New("produce error"))
+
+ // Act
+ err := kp.Produce([]byte("test message"))
+
+ // Assert
+ assert.Error(t, err)
+ assert.Equal(t, "produce error", err.Error())
+ mockProducer.AssertExpectations(t)
+}
+
+func TestKafkaProducer_Close(t *testing.T) {
+ // Arrange
+ mockProducer := new(mocks.KafkaProducerInterface)
+ kp := &KafkaProducer{
+ producer: mockProducer,
+ }
+
+ // Simulate successful close
+ mockProducer.On("Close").Return()
+
+ // Act
+ kp.Close()
+
+ // Assert
+ mockProducer.AssertExpectations(t)
+}
+
+func TestKafkaProducer_Close_Error(t *testing.T) {
+ // Arrange
+ mockProducer := new(mocks.KafkaProducerInterface)
+ kp := &KafkaProducer{
+ producer: mockProducer,
+ }
+
+ // Simulate close error
+ mockProducer.On("Close").Return()
+
+ // Act
+ kp.Close()
+
+ // Assert
+ mockProducer.AssertExpectations(t)
+}
diff --git a/pkg/kafkacomm/publisher/mocks/PdpStatusSender.go b/pkg/kafkacomm/publisher/mocks/PdpStatusSender.go
new file mode 100644
index 0000000..f9cc279
--- /dev/null
+++ b/pkg/kafkacomm/publisher/mocks/PdpStatusSender.go
@@ -0,0 +1,46 @@
+// Code generated by mockery v2.46.3. DO NOT EDIT.
+
+package mocks
+
+import (
+ model "policy-opa-pdp/pkg/model"
+
+ mock "github.com/stretchr/testify/mock"
+)
+
+// PdpStatusSender is an autogenerated mock type for the PdpStatusSender type
+type PdpStatusSender struct {
+ mock.Mock
+}
+
+// SendPdpStatus provides a mock function with given fields: pdpStatus
+func (_m *PdpStatusSender) SendPdpStatus(pdpStatus model.PdpStatus) error {
+ ret := _m.Called(pdpStatus)
+
+ if len(ret) == 0 {
+ panic("no return value specified for SendPdpStatus")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(model.PdpStatus) error); ok {
+ r0 = rf(pdpStatus)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// NewPdpStatusSender creates a new instance of PdpStatusSender. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewPdpStatusSender(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *PdpStatusSender {
+ mock := &PdpStatusSender{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/pkg/kafkacomm/publisher/pdp-heartbeat.go b/pkg/kafkacomm/publisher/pdp-heartbeat.go
new file mode 100644
index 0000000..f814992
--- /dev/null
+++ b/pkg/kafkacomm/publisher/pdp-heartbeat.go
@@ -0,0 +1,111 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// The publisher package is responsible for managing periodic heartbeat messages for the
+// Open Policy Agent (OPA) Policy Decision Point (PDP) and publishing the PDP's status to relevant channels.
+// It provides functions to initialize, manage, and stop timers for sending heartbeat messages,
+// ensuring the PDP communicates its health and state periodically.
+package publisher
+
+import (
+ "fmt"
+ "policy-opa-pdp/consts"
+ "policy-opa-pdp/pkg/log"
+ "policy-opa-pdp/pkg/model"
+ "policy-opa-pdp/pkg/pdpattributes"
+ "policy-opa-pdp/pkg/pdpstate"
+ "time"
+
+ "github.com/google/uuid"
+)
+
+var (
+ ticker *time.Ticker
+ stopChan chan bool
+ currentInterval int64
+)
+
+// Initializes a timer that sends periodic heartbeat messages to indicate the health and state of the PDP.
+func StartHeartbeatIntervalTimer(intervalMs int64, s PdpStatusSender) {
+ if intervalMs <= 0 {
+ log.Errorf("Invalid interval provided: %d. Interval must be greater than zero.", intervalMs)
+ ticker = nil
+ return
+ }
+
+ if ticker != nil && intervalMs == currentInterval {
+ log.Debug("Ticker is already running")
+ return
+ }
+
+ if ticker != nil {
+ ticker.Stop()
+ }
+ // StopTicker()
+ currentInterval = intervalMs
+
+ ticker = time.NewTicker(time.Duration(intervalMs) * time.Millisecond)
+ log.Debugf("New Ticker %d", currentInterval)
+ stopChan = make(chan bool)
+ go func() {
+ for {
+ select {
+ case <-ticker.C:
+ sendPDPHeartBeat(s)
+ case <-stopChan:
+ ticker.Stop()
+ return
+ }
+ }
+ }()
+}
+
+// Creates and sends a heartbeat message with the PDP's current state, health, and attributes
+func sendPDPHeartBeat(s PdpStatusSender) error {
+ pdpStatus := model.PdpStatus{
+ MessageType: model.PDP_STATUS,
+ PdpType: consts.PdpType,
+ State: pdpstate.GetState(),
+ Healthy: model.Healthy,
+ Name: pdpattributes.PdpName,
+ Description: "Pdp heartbeat",
+ PdpGroup: consts.PdpGroup,
+ PdpSubgroup: &pdpattributes.PdpSubgroup,
+ }
+ pdpStatus.RequestID = uuid.New().String()
+ pdpStatus.TimestampMs = fmt.Sprintf("%d", time.Now().UnixMilli())
+
+ err := s.SendPdpStatus(pdpStatus)
+ log.Debugf("Sending Heartbeat ...")
+ if err != nil {
+ log.Warnf("Error producing message: %v\n", err)
+ return err
+ } else {
+ return nil
+ }
+}
+
+// Stops the running ticker and terminates the goroutine managing heartbeat messages.
+func StopTicker() {
+ if ticker != nil && stopChan != nil {
+ stopChan <- true
+ close(stopChan)
+ ticker = nil
+ } else {
+ log.Debugf("Ticker is not Running")
+ }
+}
diff --git a/pkg/kafkacomm/publisher/pdp-heartbeat_test.go b/pkg/kafkacomm/publisher/pdp-heartbeat_test.go
new file mode 100644
index 0000000..f03b0eb
--- /dev/null
+++ b/pkg/kafkacomm/publisher/pdp-heartbeat_test.go
@@ -0,0 +1,135 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 publisher
+
+import (
+ /* "fmt"
+ "policy-opa-pdp/cfg"
+ "policy-opa-pdp/consts"
+ "policy-opa-pdp/pkg/log"
+ "policy-opa-pdp/pkg/model"
+ "policy-opa-pdp/pkg/pdpstate"*/
+ "errors"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+ "policy-opa-pdp/pkg/kafkacomm/publisher/mocks"
+ "testing"
+ // "time"
+ /* "github.com/google/uuid"*/)
+
+var (
+// ticker *time.Ticker
+// stopChan chan bool
+// currentInterval int64
+)
+
+/*
+Success Case 1
+TestStartHeartbeatIntervalTimer_ValidInterval
+Description: Test starting the heartbeat interval timer with a valid interval.
+Input: intervalMs = 1000
+Expected Output: The ticker starts with an interval of 1000 milliseconds, and heartbeat messages are sent at this interval.
+*/
+func TestStartHeartbeatIntervalTimer_ValidInterval(t *testing.T) {
+
+ intervalMs := int64(1000)
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(nil)
+
+ StartHeartbeatIntervalTimer(intervalMs, mockSender)
+ if ticker == nil {
+ t.Errorf("Expected ticker to be initialized")
+ }
+ if currentInterval != intervalMs {
+ t.Errorf("Expected currentInterval to be %d, got %d", intervalMs, currentInterval)
+ }
+}
+
+/*
+Failure Case 1
+TestStartHeartbeatIntervalTimer_InvalidInterval
+Description: Test starting the heartbeat interval timer with an invalid interval.
+Input: intervalMs = -1000
+Expected Output: The function should handle the invalid interval gracefully, possibly by logging an error message and not starting the ticker.
+*/
+func TestStartHeartbeatIntervalTimer_InvalidInterval(t *testing.T) {
+ intervalMs := int64(-1000)
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(nil)
+
+ StartHeartbeatIntervalTimer(intervalMs, mockSender)
+
+ if ticker != nil {
+ t.Log("Expected ticker to be nil for invalid interval")
+ }
+}
+
+/*
+TestSendPDPHeartBeat_Success 2
+Description: Test sending a heartbeat successfully.
+Input: Valid pdpStatus object
+Expected Output: Heartbeat message is sent successfully, and a debug log "Message sent successfully" is generated.
+*/
+func TestSendPDPHeartBeat_Success(t *testing.T) {
+
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(nil)
+ err := sendPDPHeartBeat(mockSender)
+ assert.NoError(t, err)
+}
+
+/*
+TestSendPDPHeartBeat_Failure 2
+Description: Test failing to send a heartbeat.
+Input: Invalid pdpStatus object or network failure
+Expected Output: An error occurs while sending the heartbeat, and a warning log "Error producing message: ..." is generated.
+*/
+func TestSendPDPHeartBeat_Failure(t *testing.T) {
+ // Mock SendPdpStatus to return an error
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(errors.New("Error producing message"))
+ err := sendPDPHeartBeat(mockSender)
+ assert.Error(t, err)
+}
+
+/*
+TestStopTicker_Success 3
+Description: Test stopping the ticker.
+Input: Ticker is running
+Expected Output: The ticker stops, and the stop channel is closed.
+*/
+func TestStopTicker_Success(t *testing.T) {
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(nil)
+ StartHeartbeatIntervalTimer(1000, mockSender)
+ StopTicker()
+ if ticker != nil {
+ t.Errorf("Expected ticker to be nil")
+ }
+}
+
+/*
+TestStopTicker_NotRunning 3
+Description: Test stopping the ticker when it is not running.
+Input: Ticker is not running
+Expected Output: The function should handle this case gracefully, possibly by logging a debug message indicating that the ticker is not running.
+*/
+func TestStopTicker_NotRunning(t *testing.T) {
+ StopTicker()
+}
diff --git a/pkg/kafkacomm/publisher/pdp-pap-registration.go b/pkg/kafkacomm/publisher/pdp-pap-registration.go
new file mode 100644
index 0000000..75f22d6
--- /dev/null
+++ b/pkg/kafkacomm/publisher/pdp-pap-registration.go
@@ -0,0 +1,95 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// allows to send the pdp registartion message with unique transaction id and timestamp to topic
+package publisher
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/google/uuid"
+ "policy-opa-pdp/cfg"
+ "policy-opa-pdp/consts"
+ "policy-opa-pdp/pkg/kafkacomm"
+ "policy-opa-pdp/pkg/log"
+ "policy-opa-pdp/pkg/model"
+ "policy-opa-pdp/pkg/pdpattributes"
+ "time"
+)
+
+type PdpStatusSender interface {
+ SendPdpStatus(pdpStatus model.PdpStatus) error
+}
+
+type RealPdpStatusSender struct{}
+
+// Sends PdpSTatus Message type to KafkaTopic
+func (s *RealPdpStatusSender) SendPdpStatus(pdpStatus model.PdpStatus) error {
+
+ var topic string
+ bootstrapServers := cfg.BootstrapServer
+ topic = cfg.Topic
+ pdpStatus.RequestID = uuid.New().String()
+ pdpStatus.TimestampMs = fmt.Sprintf("%d", time.Now().UnixMilli())
+
+ jsonMessage, err := json.Marshal(pdpStatus)
+ if err != nil {
+ log.Warnf("failed to marshal PdpStatus to JSON: %v", err)
+ return err
+ }
+
+ producer, err := kafkacomm.GetKafkaProducer(bootstrapServers, topic)
+ if err != nil {
+ log.Warnf("Error creating Kafka producer: %v\n", err)
+ return err
+ }
+
+ err = producer.Produce(jsonMessage)
+ if err != nil {
+ log.Warnf("Error producing message: %v\n", err)
+ } else {
+ log.Debugf("[OUT|KAFKA|%s]\n%s", topic, string(jsonMessage))
+ }
+
+ return nil
+}
+
+// sends the registartion message to topic using SendPdpStatus(pdpStatus)
+func SendPdpPapRegistration(s PdpStatusSender) error {
+
+ var pdpStatus = model.PdpStatus{
+ MessageType: model.PDP_STATUS,
+ PdpType: consts.PdpType,
+ State: model.Passive,
+ Healthy: model.Healthy,
+ Policies: nil,
+ PdpResponse: nil,
+ Name: pdpattributes.PdpName,
+ Description: "Pdp Status Registration Message",
+ PdpGroup: consts.PdpGroup,
+ }
+
+ log.Debugf("Sending PDP PAP Registration Message")
+
+ err := s.SendPdpStatus(pdpStatus)
+ if err != nil {
+ log.Warnf("Error producing message: %v\n", err)
+ return err
+ }
+ return nil
+
+}
diff --git a/pkg/kafkacomm/publisher/pdp-pap-registration_test.go b/pkg/kafkacomm/publisher/pdp-pap-registration_test.go
new file mode 100644
index 0000000..03749de
--- /dev/null
+++ b/pkg/kafkacomm/publisher/pdp-pap-registration_test.go
@@ -0,0 +1,58 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 publisher
+
+import (
+ "errors"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+ "policy-opa-pdp/pkg/kafkacomm/publisher/mocks"
+ "policy-opa-pdp/pkg/model"
+ "testing"
+)
+
+type MockPdpStatusSender struct {
+ mock.Mock
+}
+
+func (m *MockPdpStatusSender) SendPdpStatus(pdpStatus model.PdpStatus) error {
+ return m.Called(pdpStatus).Error(0)
+
+}
+
+func TestSendPdpPapRegistration_Success(t *testing.T) {
+ mockSender := new(mocks.PdpStatusSender)
+
+ mockSender.On("SendPdpStatus", mock.AnythingOfType("model.PdpStatus")).Return(nil)
+
+ err := SendPdpPapRegistration(mockSender)
+ assert.NoError(t, err)
+ mockSender.AssertCalled(t, "SendPdpStatus", mock.AnythingOfType("model.PdpStatus"))
+}
+
+func TestSendPdpPapRegistration_Failure(t *testing.T) {
+ mockSender := new(mocks.PdpStatusSender)
+
+ mockSender.On("SendPdpStatus", mock.AnythingOfType("model.PdpStatus")).Return(errors.New("failed To Send"))
+
+ err := SendPdpPapRegistration(mockSender)
+ assert.Error(t, err, "Expected an error for failure")
+ assert.EqualError(t, err, "failed To Send", "Error messages should match")
+ mockSender.AssertCalled(t, "SendPdpStatus", mock.AnythingOfType("model.PdpStatus"))
+}
diff --git a/pkg/kafkacomm/publisher/pdp-status-publisher.go b/pkg/kafkacomm/publisher/pdp-status-publisher.go
new file mode 100644
index 0000000..756d0f2
--- /dev/null
+++ b/pkg/kafkacomm/publisher/pdp-status-publisher.go
@@ -0,0 +1,109 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+//
+
+// responsible for sending PDP_STATUS messages in response to specific events
+// such as updates (PDP_UPDATE) or state changes (PDP_STATE_CHANGE). These responses provide details
+// about the current state, health, and attributes of the Policy Decision Point (PDP).
+package publisher
+
+import (
+ "fmt"
+ "policy-opa-pdp/consts"
+ "policy-opa-pdp/pkg/log"
+ "policy-opa-pdp/pkg/model"
+ "policy-opa-pdp/pkg/pdpattributes"
+ "policy-opa-pdp/pkg/pdpstate"
+ "time"
+
+ "github.com/google/uuid"
+)
+
+// Sends a PDP_STATUS message to indicate the successful processing of a PDP_UPDATE request
+// received from the Policy Administration Point (PAP).
+func SendPdpUpdateResponse(s PdpStatusSender, pdpUpdate *model.PdpUpdate) error {
+
+ responseStatus := model.Success
+ responseMessage := "PDP Update was Successful"
+
+ pdpStatus := model.PdpStatus{
+ MessageType: model.PDP_STATUS,
+ PdpType: consts.PdpType,
+ State: pdpstate.State,
+ Healthy: model.Healthy,
+ Name: pdpattributes.PdpName,
+ Description: "Pdp Status Response Message For Pdp Update",
+ PdpGroup: consts.PdpGroup,
+ PdpSubgroup: &pdpattributes.PdpSubgroup,
+ // Policies: [],
+ PdpResponse: &model.PdpResponseDetails{
+ ResponseTo: &pdpUpdate.RequestId,
+ ResponseStatus: &responseStatus,
+ ResponseMessage: &responseMessage,
+ },
+ }
+
+ pdpStatus.RequestID = uuid.New().String()
+ pdpStatus.TimestampMs = fmt.Sprintf("%d", time.Now().UnixMilli())
+
+ log.Infof("Sending PDP Status With Update Response")
+
+ err := s.SendPdpStatus(pdpStatus)
+ if err != nil {
+ log.Warnf("Failed to send PDP Update Message : %v", err)
+ return err
+ }
+
+ return nil
+
+}
+
+// Sends a PDP_STATUS message to indicate a state change in the PDP (e.g., from PASSIVE to ACTIVE).
+func SendStateChangeResponse(s PdpStatusSender, pdpStateChange *model.PdpStateChange) error {
+
+ responseStatus := model.Success
+ responseMessage := "PDP State Changed From PASSIVE TO Active"
+ pdpStatus := model.PdpStatus{
+ MessageType: model.PDP_STATUS,
+ PdpType: consts.PdpType,
+ State: pdpstate.GetState(),
+ Healthy: model.Healthy,
+ Name: pdpattributes.PdpName,
+ Description: "Pdp Status Response Message to Pdp State Change",
+ PdpGroup: consts.PdpGroup,
+ PdpSubgroup: &pdpattributes.PdpSubgroup,
+ // Policies: [],
+ PdpResponse: &model.PdpResponseDetails{
+ ResponseTo: &pdpStateChange.RequestId,
+ ResponseStatus: &responseStatus,
+ ResponseMessage: &responseMessage,
+ },
+ }
+
+ pdpStatus.RequestID = uuid.New().String()
+ pdpStatus.TimestampMs = fmt.Sprintf("%d", time.Now().UnixMilli())
+
+ log.Infof("Sending PDP Status With State Change response")
+
+ err := s.SendPdpStatus(pdpStatus)
+ if err != nil {
+ log.Warnf("Failed to send PDP Update Message : %v", err)
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/kafkacomm/publisher/pdp-status-publisher_test.go b/pkg/kafkacomm/publisher/pdp-status-publisher_test.go
new file mode 100644
index 0000000..5e02704
--- /dev/null
+++ b/pkg/kafkacomm/publisher/pdp-status-publisher_test.go
@@ -0,0 +1,83 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 publisher
+
+import (
+ "errors"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+ "policy-opa-pdp/pkg/kafkacomm/publisher/mocks"
+ "policy-opa-pdp/pkg/model"
+ "testing"
+)
+
+// TestSendPdpUpdateResponse_Success tests SendPdpUpdateResponse for a successful response
+func TestSendPdpUpdateResponse_Success(t *testing.T) {
+
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(nil)
+ pdpUpdate := &model.PdpUpdate{RequestId: "test-request-id"}
+
+ err := SendPdpUpdateResponse(mockSender, pdpUpdate)
+ assert.NoError(t, err)
+ mockSender.AssertCalled(t, "SendPdpStatus", mock.Anything)
+}
+
+// TestSendPdpUpdateResponse_Failure tests SendPdpUpdateResponse when SendPdpStatus fails
+func TestSendPdpUpdateResponse_Failure(t *testing.T) {
+
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(errors.New("mock send error"))
+
+ pdpUpdate := &model.PdpUpdate{RequestId: "test-request-id"}
+
+ err := SendPdpUpdateResponse(mockSender, pdpUpdate)
+
+ assert.Error(t, err)
+
+ mockSender.AssertCalled(t, "SendPdpStatus", mock.Anything)
+}
+
+// TestSendStateChangeResponse_Success tests SendStateChangeResponse for a successful state change response
+func TestSendStateChangeResponse_Success(t *testing.T) {
+
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(nil)
+
+ pdpStateChange := &model.PdpStateChange{RequestId: "test-state-change-id"}
+
+ err := SendStateChangeResponse(mockSender, pdpStateChange)
+
+ assert.NoError(t, err)
+ mockSender.AssertCalled(t, "SendPdpStatus", mock.Anything)
+}
+
+// TestSendStateChangeResponse_Failure tests SendStateChangeResponse when SendPdpStatus fails
+func TestSendStateChangeResponse_Failure(t *testing.T) {
+
+ mockSender := new(mocks.PdpStatusSender)
+ mockSender.On("SendPdpStatus", mock.Anything).Return(errors.New("mock send error"))
+
+ pdpStateChange := &model.PdpStateChange{RequestId: "test-state-change-id"}
+
+ err := SendStateChangeResponse(mockSender, pdpStateChange)
+ assert.Error(t, err)
+ mockSender.AssertCalled(t, "SendPdpStatus", mock.Anything)
+
+}
diff --git a/pkg/log/log.go b/pkg/log/log.go
new file mode 100644
index 0000000..2a8b997
--- /dev/null
+++ b/pkg/log/log.go
@@ -0,0 +1,131 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 log
+
+import (
+ "github.com/sirupsen/logrus"
+ "gopkg.in/natefinch/lumberjack.v2"
+ "io"
+ "os"
+ "policy-opa-pdp/cfg"
+ "policy-opa-pdp/consts"
+)
+
+type Logger struct {
+ *logrus.Logger
+}
+
+var (
+ Log *Logger
+)
+
+func SetOutput(w io.Writer) {
+ Log.SetOutput(w)
+}
+
+func init() {
+ Log = InitLogger(consts.LogFilePath, consts.LogMaxSize, consts.LogMaxBackups, cfg.LogLevel)
+}
+
+func InitLogger(logFilePath string, logMaxSize int, logMaxBackups int, logLevel string) *Logger {
+ log := logrus.New()
+
+ log.SetLevel(logrus.DebugLevel)
+ log.SetOutput(os.Stdout)
+
+ logLevelParsed, err := logrus.ParseLevel(logLevel)
+ if err != nil {
+ log.Warn(err)
+ }
+ log.SetLevel(logLevelParsed)
+
+ logRotation := &lumberjack.Logger{
+ Filename: consts.LogFilePath,
+ MaxSize: consts.LogMaxSize,
+ MaxBackups: consts.LogMaxBackups,
+ }
+ multiWriter := io.MultiWriter(os.Stdout, logRotation)
+ log.SetOutput(multiWriter)
+
+ log.SetFormatter(&logrus.TextFormatter{
+ ForceColors: true,
+ DisableColors: false,
+ FullTimestamp: true,
+ TimestampFormat: "2006-01-02T15:04:05.0000-07:00",
+ })
+
+ log.Debugf("logger initialised Filepath = %s, Logsize(MB) = %d, Backups = %d, Loglevel = %s", logFilePath, logMaxSize, logMaxBackups, logLevel)
+ return &Logger{log}
+}
+
+func ParseLevel(level string) (logrus.Level, error) {
+ return logrus.ParseLevel(level)
+}
+
+func SetLevel(level logrus.Level) {
+ Log.SetLevel(level)
+}
+
+func Error(args ...interface{}) {
+ Log.Error(args...)
+}
+
+func Info(args ...interface{}) {
+ Log.Info(args...)
+}
+
+func Debug(args ...interface{}) {
+ Log.Debug(args...)
+}
+
+func Warn(args ...interface{}) {
+ Log.Warn(args...)
+}
+
+func Panic(args ...interface{}) {
+ Log.Panic(args...)
+}
+
+func Trace(args ...interface{}) {
+ Log.Trace(args...)
+}
+
+func Errorf(msg string, args ...interface{}) {
+ Log.Errorf(msg, args...)
+}
+
+func Infof(msg string, args ...interface{}) {
+ Log.Infof(msg, args...)
+}
+
+func Debugf(msg string, args ...interface{}) {
+ Log.Debugf(msg, args...)
+}
+
+func Warnf(msg string, args ...interface{}) {
+ Log.Warnf(msg, args...)
+}
+
+func Panicf(msg string, args ...interface{}) {
+ Log.Panicf(msg, args...)
+}
+
+func Tracef(msg string, args ...interface{}) {
+ Log.Tracef(msg, args...)
+}
diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go
new file mode 100644
index 0000000..d24274c
--- /dev/null
+++ b/pkg/log/log_test.go
@@ -0,0 +1,354 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 log_test
+
+import (
+ "testing"
+
+ "bytes"
+ "github.com/sirupsen/logrus"
+ "policy-opa-pdp/pkg/log"
+)
+
+func TestSetOutput_Success(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.InfoLevel)
+
+ log.Info("Testing SetOutput")
+ if !bytes.Contains(buf.Bytes(), []byte("Testing SetOutput")) {
+ t.Errorf("Expected message to be logged")
+ }
+}
+
+func TestInit_Success(t *testing.T) {
+ var buf bytes.Buffer
+
+ log.SetOutput(&buf)
+ log.InitLogger("/tmp/logfile.log", 10, 5, "debug")
+ log.Info("Logger initialized")
+
+ if !bytes.Contains(buf.Bytes(), []byte("Logger initialized")) {
+ t.Errorf("Expected message to be logged after initialization")
+ }
+}
+
+func TestInitLogger_Success(t *testing.T) {
+ var buf bytes.Buffer
+
+ log.SetOutput(&buf)
+
+ log.InitLogger("/tmp/logfile.log", 10, 5, "info")
+
+ log.Info("Logger Initialized Test")
+ if !bytes.Contains(buf.Bytes(), []byte("Logger Initialized Test")) {
+ t.Errorf("Expected message to be logged")
+ }
+}
+
+func TestParseLevel_Success(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+
+ level, err := logrus.ParseLevel("info")
+ if err != nil {
+ t.Fatalf("Failed to parse log level: %v", err)
+ }
+ log.SetLevel(level)
+
+ log.Info("Info level set")
+
+ if !bytes.Contains(buf.Bytes(), []byte("Info level set")) {
+ t.Errorf("Expected info level to be set")
+ }
+}
+
+func TestSetLevel_Success(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.DebugLevel)
+
+ log.Debug("This is a debug message")
+ if !bytes.Contains(buf.Bytes(), []byte("This is a debug message")) {
+ t.Errorf("Expected debug message to be logged")
+ }
+}
+
+func TestError_Success(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.ErrorLevel)
+
+ log.Error("This is an error message")
+ if !bytes.Contains(buf.Bytes(), []byte("This is an error message")) {
+ t.Errorf("Expected error message to be logged")
+ }
+}
+
+func TestInfo_Success(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.InfoLevel)
+
+ log.Info("This is an info message")
+ if !bytes.Contains(buf.Bytes(), []byte("This is an info message")) {
+ t.Errorf("Expected info message to be logged")
+ }
+}
+
+func TestDebug_Success(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.DebugLevel)
+
+ log.Debug("This is a debug message")
+ if !bytes.Contains(buf.Bytes(), []byte("This is a debug message")) {
+ t.Errorf("Expected debug message to be logged")
+ }
+}
+
+func TestWarn_Success(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.WarnLevel)
+
+ log.Warn("This is a warning message")
+ if !bytes.Contains(buf.Bytes(), []byte("This is a warning message")) {
+ t.Errorf("Expected warning message to be logged")
+ }
+}
+
+func TestPanic_Success(t *testing.T) {
+ defer func() {
+ if r := recover(); r == nil {
+ t.Errorf("Expected panic, but did not get one")
+ }
+ }()
+
+ log.SetLevel(logrus.PanicLevel)
+ log.Panic("This is a panic message")
+}
+
+func TestTrace_Success(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.TraceLevel)
+
+ log.Trace("This is a trace message")
+ if !bytes.Contains(buf.Bytes(), []byte("This is a trace message")) {
+ t.Errorf("Expected trace message to be logged")
+ }
+}
+
+func TestErrorf_Success(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.ErrorLevel)
+
+ log.Errorf("Error occurred: %s", "test error")
+ if !bytes.Contains(buf.Bytes(), []byte("Error occurred: test error")) {
+ t.Errorf("Expected error message to be logged")
+ }
+}
+
+func TestInfof_Success(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.InfoLevel)
+
+ log.Infof("Info log: %s", "test info")
+ if !bytes.Contains(buf.Bytes(), []byte("Info log: test info")) {
+ t.Errorf("Expected info message to be logged")
+ }
+}
+
+func TestDebugf_Success(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.DebugLevel)
+
+ log.Debugf("Debug message: %s", "should log")
+ if !bytes.Contains(buf.Bytes(), []byte("Debug message: should log")) {
+ t.Errorf("Expected debug message to be logged")
+ }
+}
+
+func TestWarnf_Success(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.WarnLevel)
+
+ log.Warnf("Warning message: %s", "should log")
+ if !bytes.Contains(buf.Bytes(), []byte("Warning message: should log")) {
+ t.Errorf("Expected warning message to be logged")
+ }
+}
+
+func TestPanicf_Success(t *testing.T) {
+ defer func() {
+ if r := recover(); r == nil {
+ t.Errorf("Expected panic, but did not get one")
+ }
+ }()
+
+ log.SetLevel(logrus.PanicLevel)
+ log.Panicf("Panic message: %s", "should panic")
+}
+
+func TestTracef_Success(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.TraceLevel)
+
+ log.Tracef("Trace message: %s", "should log")
+ if !bytes.Contains(buf.Bytes(), []byte("Trace message: should log")) {
+ t.Errorf("Expected trace message to be logged")
+ }
+}
+
+func TestError_Failure(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.FatalLevel) // Set level higher than Error
+
+ log.Error("This is an error message")
+ if bytes.Contains(buf.Bytes(), []byte("This is an error message")) {
+ t.Errorf("Expected error message not to be logged")
+ }
+}
+
+func TestInfo_Failure(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.WarnLevel) // Set level higher than Info
+
+ log.Info("This is an info message")
+ if bytes.Contains(buf.Bytes(), []byte("This is an info message")) {
+ t.Errorf("Expected info message not to be logged")
+ }
+}
+
+func TestDebug_Failure(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.InfoLevel) // Set level higher than Debug
+
+ log.Debug("This is a debug message")
+ if bytes.Contains(buf.Bytes(), []byte("This is a debug message")) {
+ t.Errorf("Expected debug message not to be logged")
+ }
+}
+
+func TestWarn_Failure(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.ErrorLevel) // Set level higher than Warn
+
+ log.Warn("This is a warning message")
+ if bytes.Contains(buf.Bytes(), []byte("This is a warning message")) {
+ t.Errorf("Expected warning message not to be logged")
+ }
+}
+
+func TestPanic_Failure(t *testing.T) {
+ defer func() {
+ if r := recover(); r == nil {
+ t.Errorf("Expected a panic at PanicLevel, but did not get one")
+ }
+ }()
+ log.SetLevel(logrus.PanicLevel) // Set to PanicLevel so a panic should occur
+ log.Panic("This should cause a panic at PanicLevel")
+}
+
+func TestTrace_Failure(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.DebugLevel) // Set level higher than Trace
+
+ log.Trace("This is a trace message")
+ if bytes.Contains(buf.Bytes(), []byte("This is a trace message")) {
+ t.Errorf("Expected trace message not to be logged")
+ }
+}
+
+func TestErrorf_Failure(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.FatalLevel) // Set level higher than Error
+
+ log.Errorf("Error occurred: %s", "test error")
+ if bytes.Contains(buf.Bytes(), []byte("Error occurred: test error")) {
+ t.Errorf("Expected error message not to be logged")
+ }
+}
+
+func TestInfof_Failure(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.WarnLevel) // Set level higher than Info
+
+ log.Infof("Info log: %s", "test info")
+ if bytes.Contains(buf.Bytes(), []byte("Info log: test info")) {
+ t.Errorf("Expected info message not to be logged")
+ }
+}
+
+func TestDebugf_Failure(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.InfoLevel) // Set level higher than Debug
+
+ log.Debugf("Debug message: %s", "should not log")
+ if bytes.Contains(buf.Bytes(), []byte("Debug message: should not log")) {
+ t.Errorf("Expected debug message not to be logged")
+ }
+}
+
+func TestWarnf_Failure(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.ErrorLevel) // Set level higher than Warn
+
+ log.Warnf("Warning message: %s", "should not log")
+ if bytes.Contains(buf.Bytes(), []byte("Warning message: should not log")) {
+ t.Errorf("Expected warning message not to be logged")
+ }
+}
+
+func TestPanicf_Failure(t *testing.T) {
+ defer func() {
+ if r := recover(); r == nil {
+ t.Errorf("Expected a panic at PanicLevel, but did not get one")
+ }
+ }()
+
+ log.SetLevel(logrus.PanicLevel) // Set to PanicLevel so a panic should occur
+ log.Panicf("Panicf message: %s", "should panic at PanicLevel")
+}
+
+func TestTracef_Failure(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ log.SetLevel(logrus.DebugLevel) // Set level higher than Trace
+
+ log.Tracef("Trace message: %s", "should not log")
+ if bytes.Contains(buf.Bytes(), []byte("Trace message: should not log")) {
+ t.Errorf("Expected trace message not to be logged")
+ }
+}
diff --git a/pkg/metrics/counters.go b/pkg/metrics/counters.go
new file mode 100644
index 0000000..2fc9539
--- /dev/null
+++ b/pkg/metrics/counters.go
@@ -0,0 +1,66 @@
+package metrics
+
+import "sync"
+
+//global counter variables
+var IndeterminantDecisionsCount int64
+var PermitDecisionsCount int64
+var DenyDecisionsCount int64
+var TotalErrorCount int64
+var mu sync.Mutex
+
+// Increment counter
+func IncrementIndeterminantDecisionsCount() {
+ mu.Lock()
+ IndeterminantDecisionsCount++
+ mu.Unlock()
+}
+
+// returns pointer to the counter
+func IndeterminantDecisionsCountRef() *int64 {
+ mu.Lock()
+ defer mu.Unlock()
+ return &IndeterminantDecisionsCount
+}
+
+// Increment counter
+func IncrementPermitDecisionsCount() {
+ mu.Lock()
+ PermitDecisionsCount++
+ mu.Unlock()
+}
+
+// returns pointer to the counter
+func PermitDecisionsCountRef() *int64 {
+ mu.Lock()
+ defer mu.Unlock()
+ return &PermitDecisionsCount
+}
+
+// Increment counter
+func IncrementDenyDecisionsCount() {
+ mu.Lock()
+ DenyDecisionsCount++
+ mu.Unlock()
+}
+
+// returns pointer to the counter
+func DenyDecisionsCountRef() *int64 {
+ mu.Lock()
+ defer mu.Unlock()
+ return &DenyDecisionsCount
+}
+
+// Increment counter
+func IncrementTotalErrorCount() {
+ mu.Lock()
+ TotalErrorCount++
+ mu.Unlock()
+}
+
+// returns pointer to the counter
+func TotalErrorCountRef() *int64 {
+ mu.Lock()
+ defer mu.Unlock()
+ return &TotalErrorCount
+}
diff --git a/pkg/metrics/counters_test.go b/pkg/metrics/counters_test.go
new file mode 100644
index 0000000..ef4c2b0
--- /dev/null
+++ b/pkg/metrics/counters_test.go
@@ -0,0 +1,60 @@
+package metrics
+
+import (
+ "sync"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestCounters(t *testing.T) {
+ var wg sync.WaitGroup
+
+ // Test IncrementIndeterminantDecisionsCount and IndeterminantDecisionsCountRef
+ IndeterminantDecisionsCount = 0
+ wg.Add(10)
+ for i := 0; i < 10; i++ {
+ go func() {
+ defer wg.Done()
+ IncrementIndeterminantDecisionsCount()
+ }()
+ }
+ wg.Wait()
+ assert.Equal(t, int64(10), *IndeterminantDecisionsCountRef())
+
+ // Test IncrementPermitDecisionsCount and PermitDecisionsCountRef
+ PermitDecisionsCount = 0
+ wg.Add(15)
+ for i := 0; i < 15; i++ {
+ go func() {
+ defer wg.Done()
+ IncrementPermitDecisionsCount()
+ }()
+ }
+ wg.Wait()
+ assert.Equal(t, int64(15), *PermitDecisionsCountRef())
+
+ // Test IncrementDenyDecisionsCount and DenyDecisionsCountRef
+ DenyDecisionsCount = 0
+ wg.Add(20)
+ for i := 0; i < 20; i++ {
+ go func() {
+ defer wg.Done()
+ IncrementDenyDecisionsCount()
+ }()
+ }
+ wg.Wait()
+ assert.Equal(t, int64(20), *DenyDecisionsCountRef())
+
+ // Test IncrementTotalErrorCount and TotalErrorCountRef
+ TotalErrorCount = 0
+ wg.Add(5)
+ for i := 0; i < 5; i++ {
+ go func() {
+ defer wg.Done()
+ IncrementTotalErrorCount()
+ }()
+ }
+ wg.Wait()
+ assert.Equal(t, int64(5), *TotalErrorCountRef())
+}
diff --git a/pkg/metrics/statistics-provider.go b/pkg/metrics/statistics-provider.go
new file mode 100644
index 0000000..67cee79
--- /dev/null
+++ b/pkg/metrics/statistics-provider.go
@@ -0,0 +1,65 @@
+// Handles an HTTP request to fetch the current system statistics.
+// It aggregates various decision counts (e.g., indeterminate, permit, deny)
+// and error counts into a structured response and sends it back to the client in JSON format.
+package metrics
+
+import (
+ "encoding/json"
+ "net/http"
+ "policy-opa-pdp/pkg/log"
+ "policy-opa-pdp/pkg/model/oapicodegen"
+ "policy-opa-pdp/pkg/utils"
+
+ "github.com/google/uuid"
+ openapi_types "github.com/oapi-codegen/runtime/types"
+)
+
+func FetchCurrentStatistics(res http.ResponseWriter, req *http.Request) {
+
+ requestId := req.Header.Get("X-ONAP-RequestID")
+ var parsedUUID *uuid.UUID
+ var statisticsParams *oapicodegen.StatisticsParams
+
+ if requestId != "" && utils.IsValidUUID(requestId) {
+ tempUUID, err := uuid.Parse(requestId)
+ if err != nil {
+ log.Warnf("Error Parsing the requestID: %v", err)
+ } else {
+ parsedUUID = &tempUUID
+ statisticsParams = &oapicodegen.StatisticsParams{
+ XONAPRequestID: (*openapi_types.UUID)(parsedUUID),
+ }
+ res.Header().Set("X-ONAP-RequestID", statisticsParams.XONAPRequestID.String())
+ }
+ } else {
+ log.Warnf("Invalid or Missing Request ID")
+ requestId = "000000000000"
+ res.Header().Set("X-ONAP-RequestID", requestId)
+ }
+
+ var statReport oapicodegen.StatisticsReport
+
+ statReport.IndeterminantDecisionsCount = IndeterminantDecisionsCountRef()
+ statReport.PermitDecisionsCount = PermitDecisionsCountRef()
+ statReport.DenyDecisionsCount = DenyDecisionsCountRef()
+ statReport.TotalErrorCount = TotalErrorCountRef()
+
+ // not implemented hardcoding the values to zero
+ // will be implemeneted in phase-2
+ zerovalue := int64(0)
+ onevalue := int64(1)
+ statReport.TotalPoliciesCount = &zerovalue
+ statReport.TotalPolicyTypesCount = &onevalue
+ statReport.DeployFailureCount = &zerovalue
+ statReport.DeploySuccessCount = &zerovalue
+ statReport.UndeployFailureCount = &zerovalue
+ statReport.UndeploySuccessCount = &zerovalue
+
+ value := int32(200)
+ statReport.Code = &value
+
+ res.Header().Set("Content-Type", "application/json")
+ res.WriteHeader(http.StatusOK)
+ json.NewEncoder(res).Encode(statReport)
+
+}
diff --git a/pkg/metrics/statistics-provider_test.go b/pkg/metrics/statistics-provider_test.go
new file mode 100644
index 0000000..a5e57b6
--- /dev/null
+++ b/pkg/metrics/statistics-provider_test.go
@@ -0,0 +1,51 @@
+package metrics
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "policy-opa-pdp/pkg/model/oapicodegen"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestFetchCurrentStatistics(t *testing.T) {
+
+ IndeterminantDecisionsCount = 10
+ PermitDecisionsCount = 15
+ DenyDecisionsCount = 20
+ TotalErrorCount = 5
+
+ // Create a new HTTP request
+ req := httptest.NewRequest(http.MethodGet, "/statistics", nil)
+ // Create a response recorder to capture the response
+ res := httptest.NewRecorder()
+
+ // Call the function under test
+ FetchCurrentStatistics(res, req)
+
+ // Verify the status code
+ assert.Equal(t, http.StatusOK, res.Code)
+
+ // Verify the response headers
+ assert.Equal(t, "application/json", res.Header().Get("Content-Type"))
+
+ var statReport oapicodegen.StatisticsReport
+ err := json.Unmarshal(res.Body.Bytes(), &statReport)
+ assert.NoError(t, err)
+
+ // Verify the response body
+ assert.Equal(t, int64(10), *statReport.IndeterminantDecisionsCount)
+ assert.Equal(t, int64(15), *statReport.PermitDecisionsCount)
+ assert.Equal(t, int64(20), *statReport.DenyDecisionsCount)
+ assert.Equal(t, int64(5), *statReport.TotalErrorCount)
+ assert.Equal(t, int64(0), *statReport.TotalPoliciesCount)
+ assert.Equal(t, int64(1), *statReport.TotalPolicyTypesCount)
+ assert.Equal(t, int64(0), *statReport.DeployFailureCount)
+ assert.Equal(t, int64(0), *statReport.DeploySuccessCount)
+ assert.Equal(t, int64(0), *statReport.UndeployFailureCount)
+ assert.Equal(t, int64(0), *statReport.UndeploySuccessCount)
+
+ assert.Equal(t, int32(200), *statReport.Code)
+}
diff --git a/pkg/model/healthcheckmessage.go b/pkg/model/healthcheckmessage.go
new file mode 100644
index 0000000..8b0d9db
--- /dev/null
+++ b/pkg/model/healthcheckmessage.go
@@ -0,0 +1,27 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 model
+
+type HealthCheckResponse struct {
+ Name string `json:"name"`
+ Url string `json:"url"`
+ Healthy bool `json:"healthy"`
+ Code int `json:"code"`
+ Message string `json:"message"`
+}
diff --git a/pkg/model/mesages.go b/pkg/model/mesages.go
new file mode 100644
index 0000000..a4451d7
--- /dev/null
+++ b/pkg/model/mesages.go
@@ -0,0 +1,111 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// Defines structure for messages exchanged between PDP and PAP
+// Refer: https://docs.onap.org/projects/onap-policy-parent/en/latest/pap/InternalPapPdp.html
+// for attribute level details of each message.
+package model
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+// PdpMessageType represents the type of PDP message.
+type PdpMessageType int
+
+// Enumerate the possible PDP message types
+// https://github.com/onap/policy-models
+// models-pdp/src/main/java/org/onap/policy/models/pdp/enums/PdpMessageType.java
+const (
+ PDP_STATUS PdpMessageType = iota
+ PDP_UPDATE
+ PDP_STATE_CHANGE
+ PDP_HEALTH_CHECK
+ PDP_TOPIC_CHECK
+)
+
+// String representation of PdpMessageType
+func (msgType PdpMessageType) String() string {
+ switch msgType {
+ case PDP_STATUS:
+ return "PDP_STATUS"
+ case PDP_UPDATE:
+ return "PDP_UPDATE"
+ case PDP_STATE_CHANGE:
+ return "PDP_STATE_CHANGE"
+ case PDP_HEALTH_CHECK:
+ return "PDP_HEALTH_CHECK"
+ case PDP_TOPIC_CHECK:
+ return "PDP_TOPIC_CHECK"
+ default:
+ return fmt.Sprintf("Unknown PdpMessageType: %d", msgType)
+ }
+}
+
+func (p PdpMessageType) MarshalJSON() ([]byte, error) {
+ return json.Marshal(p.String())
+}
+
+// PdpStatus represents the PDP_STATUS message sent from PDP to PAP.
+// https://github.com/onap/policy-models
+// models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpStatus.java
+type PdpStatus struct {
+ MessageType PdpMessageType `json:"messageName"`
+ PdpType string `json:"pdpType"`
+ State PdpState `json:"state"`
+ Healthy PdpHealthStatus `json:"healthy"`
+ Description string `json:"description"`
+ PdpResponse *PdpResponseDetails `json:"response"`
+ Policies []ToscaConceptIdentifier `json:"policies"`
+ Name string `json:"name"`
+ RequestID string `json:"requestId"`
+ PdpGroup string `json:"pdpGroup"`
+ PdpSubgroup *string `json:"pdpSubgroup"`
+ TimestampMs string `json:"timestampMs"`
+ DeploymentInstanceInfo string `json:"deploymentInstanceInfo"`
+}
+
+// PDP_UPDATE sent by PAP to PDP.
+// https://github.com/onap/policy-models
+// models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpUpdate.java
+type PdpUpdate struct {
+ Source string `json:"source" validate:"required"`
+ PdpHeartbeatIntervalMs int64 `json:"pdpHeartbeatIntervalMs" validate:"required"`
+ MessageType string `json:"messageName" validate:"required"`
+ PoliciesToBeDeloyed []string `json:"policiesToBeDeployed" validate:"required"`
+ policiesToBeUndeployed []ToscaConceptIdentifier `json:"policiesToBeUndeployed"`
+ Name string `json:"name" validate:"required"`
+ TimestampMs int64 `json:"timestampMs" validate:"required"`
+ PdpGroup string `json:"pdpGroup" validate:"required"`
+ PdpSubgroup string `json:"pdpSubgroup" validate:"required"`
+ RequestId string `json:"requestId" validate:"required"`
+}
+
+// PDP_STATE_CHANGE sent by PAP to PDP.
+// https://github.com/onap/policy-models
+// models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpStateChange.java
+type PdpStateChange struct {
+ Source string `json:"source"`
+ State string `json:"state"`
+ MessageType string `json:"messageName"`
+ Name string `json:"name"`
+ TimestampMs int64 `json:"timestampMs"`
+ PdpGroup string `json:"pdpGroup"`
+ PdpSubgroup string `json:"pdpSubgroup"`
+ RequestId string `json:"requestId"`
+}
diff --git a/pkg/model/messages_test.go b/pkg/model/messages_test.go
new file mode 100644
index 0000000..f6bb5ca
--- /dev/null
+++ b/pkg/model/messages_test.go
@@ -0,0 +1,241 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 model
+
+import (
+ "encoding/json"
+ "errors"
+ "testing"
+)
+
+func (p *PdpStatus) Validate() error {
+ if p.PdpType == "" {
+ return errors.New("PdpType is required")
+ }
+
+ // Check if State is set to a valid non-zero value
+ if p.State != Passive && p.State != Safe && p.State != Test && p.State != Active && p.State != Terminated {
+ return errors.New("State is required and must be a valid PdpState")
+ }
+
+ // Check if Healthy is set to a valid non-zero value
+ if p.Healthy != Healthy && p.Healthy != NotHealthy && p.Healthy != TestInProgress && p.Healthy != Unknown {
+ return errors.New("Healthy status is required and must be a valid PdpHealthStatus")
+ }
+
+ if p.Name == "" {
+ return errors.New("Name is required")
+ }
+ if p.RequestID == "" {
+ return errors.New("RequestID is required")
+ }
+ if p.PdpGroup == "" {
+ return errors.New("PdpGroup is required")
+ }
+ if p.TimestampMs == "" {
+ return errors.New("TimestampMs is required")
+ }
+
+ return nil
+}
+
+// TestPdpStatusSerialization_Positive tests the successful serialization of PdpStatus.
+func TestPdpStatusSerialization_Success(t *testing.T) {
+ pdpStatus := PdpStatus{
+ MessageType: PDP_STATUS,
+ PdpType: "examplePdpType",
+ State: Active,
+ Healthy: Healthy,
+ Description: "PDP is healthy",
+ PdpResponse: nil, // Set to nil for simplicity
+ Policies: []ToscaConceptIdentifier{},
+ Name: "ExamplePDP",
+ RequestID: "12345",
+ PdpGroup: "Group1",
+ PdpSubgroup: nil,
+ TimestampMs: "1633017600000",
+ }
+
+ _, err := json.Marshal(pdpStatus)
+ if err != nil {
+ t.Errorf("Expected no error while marshaling valid PdpStatus, got: %v", err)
+ }
+}
+
+// TestPdpStatusSerialization_Negative tests the serialization of PdpStatus with invalid fields.
+func TestPdpStatusValidation_Failure(t *testing.T) {
+ // Example of invalid state and health strings that will fail conversion
+ state, err := ConvertStringToEnumState("INVALID_STATE")
+ if err == nil {
+ t.Fatal("Expected error for invalid state")
+ }
+
+ // Example with missing fields or invalid enums
+ pdpStatus := PdpStatus{
+ PdpType: "",
+ State: state,
+ Name: "",
+ RequestID: "",
+ PdpGroup: "",
+ TimestampMs: "",
+ }
+
+ err = pdpStatus.Validate()
+ if err == nil {
+ t.Error("Expected an error while validating invalid PdpStatus, but got none")
+ }
+}
+
+func (p *PdpUpdate) Validate() error {
+ if p.Source == "" {
+ return errors.New("Source is required")
+ }
+ if p.PdpHeartbeatIntervalMs <= 0 {
+ return errors.New("PdpHeartbeatIntervalMs must be a positive integer")
+ }
+ if p.MessageType == "" {
+ return errors.New("MessageType is required")
+ }
+ if len(p.PoliciesToBeDeloyed) == 0 {
+ return errors.New("PoliciesToBeDeloyed is required and must contain at least one policy")
+ }
+ if p.Name == "" {
+ return errors.New("Name is required")
+ }
+ if p.TimestampMs <= 0 {
+ return errors.New("TimestampMs is required and must be a positive integer")
+ }
+ if p.PdpGroup == "" {
+ return errors.New("PdpGroup is required")
+ }
+ if p.PdpSubgroup == "" {
+ return errors.New("PdpSubgroup is required")
+ }
+ if p.RequestId == "" {
+ return errors.New("RequestId is required")
+ }
+
+ return nil
+}
+
+// TestPdpUpdateSerialization_Positive tests the successful serialization of PdpUpdate.
+func TestPdpUpdateSerialization_Success(t *testing.T) {
+ pdpUpdate := PdpUpdate{
+ Source: "source1",
+ PdpHeartbeatIntervalMs: 5000,
+ MessageType: "PDP_UPDATE",
+ PoliciesToBeDeloyed: []string{"policy1", "policy2"},
+ Name: "ExamplePDP",
+ TimestampMs: 1633017600000,
+ PdpGroup: "Group1",
+ PdpSubgroup: "SubGroup1",
+ RequestId: "54321",
+ }
+
+ _, err := json.Marshal(pdpUpdate)
+ if err != nil {
+ t.Errorf("Expected no error while marshaling valid PdpUpdate, got: %v", err)
+ }
+}
+
+// TestPdpUpdateSerialization_Negative tests the serialization of PdpUpdate with invalid fields.
+func TestPdpUpdateSerialization_Failure(t *testing.T) {
+ pdpUpdate := PdpUpdate{
+ Source: "",
+ PdpHeartbeatIntervalMs: 5000,
+ MessageType: "",
+ PoliciesToBeDeloyed: nil,
+ Name: "",
+ TimestampMs: 0,
+ PdpGroup: "",
+ PdpSubgroup: "",
+ RequestId: "",
+ }
+ err := pdpUpdate.Validate()
+ if err == nil {
+ t.Error("Expected an error while validating invalid PdpStatus, but got none")
+ }
+
+}
+
+func (p *PdpStateChange) Validate() error {
+ if p.Source == "" {
+ return errors.New("Source is required")
+ }
+ if p.State == "" {
+ return errors.New("State is required")
+ }
+ if p.MessageType == "" {
+ return errors.New("MessageType is required")
+ }
+ if p.Name == "" {
+ return errors.New("Name is required")
+ }
+ if p.TimestampMs <= 0 {
+ return errors.New("TimestampMs is required and must be a positive integer")
+ }
+ if p.PdpGroup == "" {
+ return errors.New("PdpGroup is required")
+ }
+ if p.PdpSubgroup == "" {
+ return errors.New("PdpSubgroup is required")
+ }
+ if p.RequestId == "" {
+ return errors.New("RequestId is required")
+ }
+
+ return nil
+}
+
+// TestPdpStateChangeSerialization_Positive tests the successful serialization of PdpStateChange.
+func TestPdpStateChangeSerialization_Success(t *testing.T) {
+ pdpStateChange := PdpStateChange{
+ Source: "source1",
+ State: "active",
+ MessageType: "PDP_STATE_CHANGE",
+ Name: "ExamplePDP",
+ TimestampMs: 1633017600000,
+ PdpGroup: "Group1",
+ PdpSubgroup: "SubGroup1",
+ RequestId: "98765",
+ }
+
+ _, err := json.Marshal(pdpStateChange)
+ if err != nil {
+ t.Errorf("Expected no error while marshaling valid PdpStateChange, got: %v", err)
+ }
+}
+
+// TestPdpStateChangeSerialization_Negative tests the serialization of PdpStateChange with invalid fields.
+func TestPdpStateChangeSerialization_Failure(t *testing.T) {
+ pdpStateChange := PdpStateChange{
+ Source: "",
+ State: "",
+ MessageType: "",
+ Name: "",
+ TimestampMs: 0,
+ PdpGroup: "",
+ PdpSubgroup: "",
+ RequestId: "",
+ }
+ err := pdpStateChange.Validate()
+ if err == nil {
+ t.Error("Expected an error while validating invalid PdpStatus, but got none")
+ }
+}
diff --git a/pkg/model/oapicodegen/models.go b/pkg/model/oapicodegen/models.go
new file mode 100644
index 0000000..4f1b770
--- /dev/null
+++ b/pkg/model/oapicodegen/models.go
@@ -0,0 +1,133 @@
+// Package api provides primitives to interact with the openapi HTTP API.
+//
+// Code generated by github.com/deepmap/oapi-codegen version v1.16.3 DO NOT EDIT.
+package oapicodegen
+
+import (
+ "time"
+
+ openapi_types "github.com/oapi-codegen/runtime/types"
+)
+
+const (
+ BasicAuthScopes = "basicAuth.Scopes"
+)
+
+// Defines values for ErrorResponseResponseCode.
+const (
+ BADGATEWAY ErrorResponseResponseCode = "BAD_GATEWAY"
+ BADREQUEST ErrorResponseResponseCode = "BAD_REQUEST"
+ CONFLICT ErrorResponseResponseCode = "CONFLICT"
+ EXPECTATIONFAILED ErrorResponseResponseCode = "EXPECTATION_FAILED"
+ GATEWAYTIMEOUT ErrorResponseResponseCode = "GATEWAY_TIMEOUT"
+ GONE ErrorResponseResponseCode = "GONE"
+ HTTPVERSIONNOTSUPPORTED ErrorResponseResponseCode = "HTTP_VERSION_NOT_SUPPORTED"
+ INTERNALSERVERERROR ErrorResponseResponseCode = "INTERNAL_SERVER_ERROR"
+ LENGTHREQUIRED ErrorResponseResponseCode = "LENGTH_REQUIRED"
+ METHODNOTALLOWED ErrorResponseResponseCode = "METHOD_NOT_ALLOWED"
+ NETWORKAUTHENTICATIONREQUIRED ErrorResponseResponseCode = "NETWORK_AUTHENTICATION_REQUIRED"
+ NOTACCEPTABLE ErrorResponseResponseCode = "NOT_ACCEPTABLE"
+ NOTIMPLEMENTED ErrorResponseResponseCode = "NOT_IMPLEMENTED"
+ PRECONDITIONFAILED ErrorResponseResponseCode = "PRECONDITION_FAILED"
+ PRECONDITIONREQUIRED ErrorResponseResponseCode = "PRECONDITION_REQUIRED"
+ REQUESTEDRANGENOTSATISFIABLE ErrorResponseResponseCode = "REQUESTED_RANGE_NOT_SATISFIABLE"
+ REQUESTENTITYTOOLARGE ErrorResponseResponseCode = "REQUEST_ENTITY_TOO_LARGE"
+ REQUESTHEADERFIELDSTOOLARGE ErrorResponseResponseCode = "REQUEST_HEADER_FIELDS_TOO_LARGE"
+ REQUESTTIMEOUT ErrorResponseResponseCode = "REQUEST_TIMEOUT"
+ REQUESTURITOOLONG ErrorResponseResponseCode = "REQUEST_URI_TOO_LONG"
+ SERVICEUNAVAILABLE ErrorResponseResponseCode = "SERVICE_UNAVAILABLE"
+ TOOMANYREQUESTS ErrorResponseResponseCode = "TOO_MANY_REQUESTS"
+ UNAUTHORIZED ErrorResponseResponseCode = "UNAUTHORIZED"
+ UNSUPPORTEDMEDIATYPE ErrorResponseResponseCode = "UNSUPPORTED_MEDIA_TYPE"
+)
+
+// Defines values for OPADecisionResponseDecision.
+const (
+ DENY OPADecisionResponseDecision = "DENY"
+ INDETERMINATE OPADecisionResponseDecision = "INDETERMINATE"
+ PERMIT OPADecisionResponseDecision = "PERMIT"
+)
+
+// ErrorResponse defines model for ErrorResponse.
+type ErrorResponse struct {
+ ErrorDetails *[]string `json:"errorDetails,omitempty"`
+ ErrorMessage *string `json:"errorMessage,omitempty"`
+ PolicyName *string `json:"policyName,omitempty"`
+ ResponseCode *ErrorResponseResponseCode `json:"responseCode,omitempty"`
+}
+
+// ErrorResponseResponseCode defines model for ErrorResponse.ResponseCode.
+type ErrorResponseResponseCode string
+
+// HealthCheckReport defines model for HealthCheckReport.
+type HealthCheckReport struct {
+ Code *int32 `json:"code,omitempty"`
+ Healthy *bool `json:"healthy,omitempty"`
+ Message *string `json:"message,omitempty"`
+ Name *string `json:"name,omitempty"`
+ Url *string `json:"url,omitempty"`
+}
+
+// OPADecisionRequest defines model for OPADecisionRequest.
+type OPADecisionRequest struct {
+ CurrentDate *openapi_types.Date `json:"currentDate,omitempty"`
+ CurrentDateTime *time.Time `json:"currentDateTime,omitempty"`
+ CurrentTime *time.Time `json:"currentTime,omitempty"`
+ Input *map[string]interface{} `json:"input,omitempty"`
+ OnapComponent *string `json:"onapComponent,omitempty"`
+ OnapInstance *string `json:"onapInstance,omitempty"`
+ OnapName *string `json:"onapName,omitempty"`
+ PolicyName *string `json:"policyName,omitempty"`
+
+ // TimeOffset Time offset in hours and minutes, e.g., '+02:00' or '-05:00'
+ TimeOffset *string `json:"timeOffset,omitempty"`
+
+ // TimeZone Timezone in IANA format (e.g., 'America/NewYork', 'Europe/Paris', 'UTC')
+ TimeZone *string `json:"timeZone,omitempty"`
+}
+
+// OPADecisionResponse defines model for OPADecisionResponse.
+type OPADecisionResponse struct {
+ Decision *OPADecisionResponseDecision `json:"decision,omitempty"`
+ PolicyName *string `json:"policyName,omitempty"`
+ StatusMessage *string `json:"statusMessage,omitempty"`
+}
+
+// OPADecisionResponseDecision defines model for OPADecisionResponse.Decision.
+type OPADecisionResponseDecision string
+
+// StatisticsReport defines model for StatisticsReport.
+type StatisticsReport struct {
+ Code *int32 `json:"code,omitempty"`
+ DenyDecisionsCount *int64 `json:"denyDecisionsCount,omitempty"`
+ DeployFailureCount *int64 `json:"deployFailureCount,omitempty"`
+ DeploySuccessCount *int64 `json:"deploySuccessCount,omitempty"`
+ IndeterminantDecisionsCount *int64 `json:"indeterminantDecisionsCount,omitempty"`
+ PermitDecisionsCount *int64 `json:"permitDecisionsCount,omitempty"`
+ TotalErrorCount *int64 `json:"totalErrorCount,omitempty"`
+ TotalPoliciesCount *int64 `json:"totalPoliciesCount,omitempty"`
+ TotalPolicyTypesCount *int64 `json:"totalPolicyTypesCount,omitempty"`
+ UndeployFailureCount *int64 `json:"undeployFailureCount,omitempty"`
+ UndeploySuccessCount *int64 `json:"undeploySuccessCount,omitempty"`
+}
+
+// DecisionParams defines parameters for Decision.
+type DecisionParams struct {
+ // XONAPRequestID RequestID for http transaction
+ XONAPRequestID *openapi_types.UUID `json:"X-ONAP-RequestID,omitempty"`
+}
+
+// HealthcheckParams defines parameters for Healthcheck.
+type HealthcheckParams struct {
+ // XONAPRequestID RequestID for http transaction
+ XONAPRequestID *openapi_types.UUID `json:"X-ONAP-RequestID,omitempty"`
+}
+
+// StatisticsParams defines parameters for Statistics.
+type StatisticsParams struct {
+ // XONAPRequestID RequestID for http transaction
+ XONAPRequestID *openapi_types.UUID `json:"X-ONAP-RequestID,omitempty"`
+}
+
+// DecisionJSONRequestBody defines body for Decision for application/json ContentType.
+type DecisionJSONRequestBody = OPADecisionRequest
diff --git a/pkg/model/pdphealthstatus.go b/pkg/model/pdphealthstatus.go
new file mode 100644
index 0000000..387a1e8
--- /dev/null
+++ b/pkg/model/pdphealthstatus.go
@@ -0,0 +1,57 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// the possible values for health status of PDP.
+// https://github.com/onap/policy-models/blob/master/models-pdp
+// models-pdp/src/main/java/org/onap/policy/models/pdp/enums/PdpHealthStatus.java
+package model
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+// PdpHealthStatus represents the possible values for the health status of PDP.
+type PdpHealthStatus int
+
+// Enumerate the possible PDP health statuses
+const (
+ Healthy PdpHealthStatus = iota
+ NotHealthy
+ TestInProgress
+ Unknown
+)
+
+// String representation of PdpHealthStatus
+func (status PdpHealthStatus) String() string {
+ switch status {
+ case Healthy:
+ return "HEALTHY"
+ case NotHealthy:
+ return "NOT_HEALTHY"
+ case TestInProgress:
+ return "TEST_IN_PROGRESS"
+ case Unknown:
+ return "UNKNOWN"
+ default:
+ return fmt.Sprintf("Unknown PdpHealthStatus: %d", status)
+ }
+}
+
+func (p PdpHealthStatus) MarshalJSON() ([]byte, error) {
+ return json.Marshal(p.String())
+}
diff --git a/pkg/model/pdphealthstatus_test.go b/pkg/model/pdphealthstatus_test.go
new file mode 100644
index 0000000..0cb89cf
--- /dev/null
+++ b/pkg/model/pdphealthstatus_test.go
@@ -0,0 +1,92 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 model
+
+import (
+ "encoding/json"
+ "testing"
+)
+
+// Positive test for the string representation of valid PdpHealthStatus values
+func TestPdpHealthStatus_String_Success(t *testing.T) {
+ tests := []struct {
+ status PdpHealthStatus
+ expected string
+ }{
+ {Healthy, "HEALTHY"},
+ {NotHealthy, "NOT_HEALTHY"},
+ {TestInProgress, "TEST_IN_PROGRESS"},
+ {Unknown, "UNKNOWN"},
+ }
+
+ for _, test := range tests {
+ if got := test.status.String(); got != test.expected {
+ t.Errorf("PdpHealthStatus.String() = %v, want %v", got, test.expected)
+ }
+ }
+}
+
+// Negative test for the string representation of an invalid PdpHealthStatus value
+func TestPdpHealthStatus_String_Failure(t *testing.T) {
+ invalidStatus := PdpHealthStatus(100)
+ expected := "Unknown PdpHealthStatus: 100"
+
+ if got := invalidStatus.String(); got != expected {
+ t.Errorf("PdpHealthStatus.String() = %v, want %v", got, expected)
+ }
+}
+
+// Positive test for JSON marshaling of valid PdpHealthStatus values
+func TestPdpHealthStatus_MarshalJSON_Success(t *testing.T) {
+ tests := []struct {
+ status PdpHealthStatus
+ expected string
+ }{
+ {Healthy, `"HEALTHY"`},
+ {NotHealthy, `"NOT_HEALTHY"`},
+ {TestInProgress, `"TEST_IN_PROGRESS"`},
+ {Unknown, `"UNKNOWN"`},
+ }
+
+ for _, test := range tests {
+ got, err := json.Marshal(test.status)
+ if err != nil {
+ t.Errorf("json.Marshal() error = %v", err)
+ }
+
+ if string(got) != test.expected {
+ t.Errorf("json.Marshal() = %v, want %v", string(got), test.expected)
+ }
+ }
+}
+
+// Negative test for JSON marshaling of an invalid PdpHealthStatus value
+func TestPdpHealthStatus_MarshalJSON_Failure(t *testing.T) {
+ invalidStatus := PdpHealthStatus(100)
+ expected := `"Unknown PdpHealthStatus: 100"`
+
+ got, err := json.Marshal(invalidStatus)
+ if err != nil {
+ t.Errorf("json.Marshal() unexpected error = %v", err)
+ }
+
+ if string(got) != expected {
+ t.Errorf("json.Marshal() = %v, want %v", string(got), expected)
+ }
+}
diff --git a/pkg/model/pdpresponsedetails.go b/pkg/model/pdpresponsedetails.go
new file mode 100644
index 0000000..8febae5
--- /dev/null
+++ b/pkg/model/pdpresponsedetails.go
@@ -0,0 +1,34 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// represent PDP response details.
+// https://github.com/onap/policy-models/blob/master/models-pdp
+// models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpResponseDetails.java
+package model
+
+type PdpResponseStatus string
+
+const (
+ Success PdpResponseStatus = "SUCCESS"
+ Failure PdpResponseStatus = "FAILURE"
+)
+
+type PdpResponseDetails struct {
+ ResponseTo *string `json:"responseTo"`
+ ResponseStatus *PdpResponseStatus `json:"responseStatus"`
+ ResponseMessage *string `json:"responseMessage"`
+}
diff --git a/pkg/model/pdpresponsedetails_test.go b/pkg/model/pdpresponsedetails_test.go
new file mode 100644
index 0000000..14b9cd8
--- /dev/null
+++ b/pkg/model/pdpresponsedetails_test.go
@@ -0,0 +1,89 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 model
+
+import (
+ "encoding/json"
+ "testing"
+)
+
+// Positive test for JSON marshaling of PdpResponseDetails with all fields populated
+func TestPdpResponseDetails_MarshalJSON_Success(t *testing.T) {
+ responseTo := "requestID123"
+ responseMessage := "Operation completed successfully"
+ responseStatus := Success
+
+ details := PdpResponseDetails{
+ ResponseTo: &responseTo,
+ ResponseStatus: &responseStatus,
+ ResponseMessage: &responseMessage,
+ }
+
+ expectedJSON := `{"responseTo":"requestID123","responseStatus":"SUCCESS","responseMessage":"Operation completed successfully"}`
+ got, err := json.Marshal(details)
+ if err != nil {
+ t.Errorf("json.Marshal() error = %v", err)
+ }
+
+ if string(got) != expectedJSON {
+ t.Errorf("json.Marshal() = %v, want %v", string(got), expectedJSON)
+ }
+}
+
+// Negative test for JSON marshaling of PdpResponseDetails with nil fields
+func TestPdpResponseDetails_MarshalJSON_Failure(t *testing.T) {
+ details := PdpResponseDetails{}
+
+ expectedJSON := `{"responseTo":null,"responseStatus":null,"responseMessage":null}`
+ got, err := json.Marshal(details)
+ if err != nil {
+ t.Errorf("json.Marshal() error = %v", err)
+ }
+
+ if string(got) != expectedJSON {
+ t.Errorf("json.Marshal() = %v, want %v", string(got), expectedJSON)
+ }
+}
+
+// Positive test for PdpResponseStatus constants
+func TestPdpResponseStatus_Success(t *testing.T) {
+ tests := []struct {
+ status PdpResponseStatus
+ expected string
+ }{
+ {Success, "SUCCESS"},
+ {Failure, "FAILURE"},
+ }
+
+ for _, test := range tests {
+ if string(test.status) != test.expected {
+ t.Errorf("PdpResponseStatus = %v, want %v", test.status, test.expected)
+ }
+ }
+}
+
+// Negative test for invalid PdpResponseStatus
+func TestPdpResponseStatus_Failure(t *testing.T) {
+ invalidStatus := PdpResponseStatus("INVALID")
+ expected := "INVALID"
+
+ if string(invalidStatus) != expected {
+ t.Errorf("PdpResponseStatus = %v, want %v", invalidStatus, expected)
+ }
+}
diff --git a/pkg/model/pdpstate.go b/pkg/model/pdpstate.go
new file mode 100644
index 0000000..2b54d16
--- /dev/null
+++ b/pkg/model/pdpstate.go
@@ -0,0 +1,77 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// hold the possible values for state of PDP.
+// https://github.com/onap/policy-models/blob/master/models-pdp
+// models-pdp/src/main/java/org/onap/policy/models/pdp/enums/PdpState.java
+package model
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+// PdpState represents the possible values for the state of PDP.
+type PdpState int
+
+// Enumerate the possible PDP states
+const (
+ Passive PdpState = iota
+ Safe
+ Test
+ Active
+ Terminated
+)
+
+// String representation of PdpState
+func (state PdpState) String() string {
+ switch state {
+ case Passive:
+ return "PASSIVE"
+ case Safe:
+ return "SAFE"
+ case Test:
+ return "TEST"
+ case Active:
+ return "ACTIVE"
+ case Terminated:
+ return "TERMINATED"
+ default:
+ return fmt.Sprintf("Unknown PdpState: %d", state)
+ }
+}
+
+func (s PdpState) MarshalJSON() ([]byte, error) {
+ return json.Marshal(s.String())
+}
+
+func ConvertStringToEnumState(state string) (PdpState, error) {
+ switch state {
+ case "PASSIVE":
+ return Passive, nil
+ case "SAFE":
+ return Safe, nil
+ case "TEST":
+ return Test, nil
+ case "ACTIVE":
+ return Active, nil
+ case "TERMINATED":
+ return Terminated, nil
+ default:
+ return -1, fmt.Errorf("Unknown PdpState: %s", state)
+ }
+}
diff --git a/pkg/model/pdpstate_test.go b/pkg/model/pdpstate_test.go
new file mode 100644
index 0000000..35ff6af
--- /dev/null
+++ b/pkg/model/pdpstate_test.go
@@ -0,0 +1,128 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 model
+
+import (
+ "encoding/json"
+ "testing"
+)
+
+// Positive test cases for PdpState.String
+func TestPdpState_String_Success(t *testing.T) {
+ tests := []struct {
+ state PdpState
+ expected string
+ }{
+ {Passive, "PASSIVE"},
+ {Safe, "SAFE"},
+ {Test, "TEST"},
+ {Active, "ACTIVE"},
+ {Terminated, "TERMINATED"},
+ }
+
+ for _, test := range tests {
+ got := test.state.String()
+ if got != test.expected {
+ t.Errorf("PdpState.String() = %v, want %v", got, test.expected)
+ }
+ }
+}
+
+// Negative test case for PdpState.String
+func TestPdpState_String_Failure(t *testing.T) {
+ state := PdpState(100) // Unknown state
+ expected := "Unknown PdpState: 100"
+ got := state.String()
+ if got != expected {
+ t.Errorf("PdpState.String() = %v, want %v", got, expected)
+ }
+}
+
+// Positive test cases for PdpState.MarshalJSON
+func TestPdpState_MarshalJSON_Success(t *testing.T) {
+ tests := []struct {
+ state PdpState
+ expected string
+ }{
+ {Passive, `"PASSIVE"`},
+ {Safe, `"SAFE"`},
+ {Test, `"TEST"`},
+ {Active, `"ACTIVE"`},
+ {Terminated, `"TERMINATED"`},
+ }
+
+ for _, test := range tests {
+ got, err := json.Marshal(test.state)
+ if err != nil {
+ t.Errorf("json.Marshal() error = %v", err)
+ continue
+ }
+
+ if string(got) != test.expected {
+ t.Errorf("json.Marshal() = %v, want %v", string(got), test.expected)
+ }
+ }
+}
+
+// Negative test case for PdpState.MarshalJSON
+func TestPdpState_MarshalJSON_Failure(t *testing.T) {
+ state := PdpState(100) // Unknown state
+ expected := `"Unknown PdpState: 100"`
+
+ got, err := json.Marshal(state)
+ if err != nil {
+ t.Errorf("json.Marshal() error = %v", err)
+ } else if string(got) != expected {
+ t.Errorf("json.Marshal() = %v, want %v", string(got), expected)
+ }
+}
+
+// Positive test cases for ConvertStringToEnumState
+func TestConvertStringToEnumState_Success(t *testing.T) {
+ tests := []struct {
+ input string
+ expected PdpState
+ }{
+ {"PASSIVE", Passive},
+ {"SAFE", Safe},
+ {"TEST", Test},
+ {"ACTIVE", Active},
+ {"TERMINATED", Terminated},
+ }
+
+ for _, test := range tests {
+ got, err := ConvertStringToEnumState(test.input)
+ if err != nil {
+ t.Errorf("ConvertStringToEnumState(%v) unexpected error = %v", test.input, err)
+ continue
+ }
+ if got != test.expected {
+ t.Errorf("ConvertStringToEnumState(%v) = %v, want %v", test.input, got, test.expected)
+ }
+ }
+}
+
+// Negative test case for ConvertStringToEnumState
+func TestConvertStringToEnumState_Failure(t *testing.T) {
+ input := "UNKNOWN" // Invalid state
+ _, err := ConvertStringToEnumState(input)
+ if err == nil {
+ t.Errorf("ConvertStringToEnumState(%v) expected error, got nil", input)
+ }
+}
diff --git a/pkg/model/toscaconceptidentifier.go b/pkg/model/toscaconceptidentifier.go
new file mode 100644
index 0000000..7afc7b1
--- /dev/null
+++ b/pkg/model/toscaconceptidentifier.go
@@ -0,0 +1,56 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// Identifies a concept. Both the name and version must be non-null.
+// https://github.com/onap/policy-models/blob/master/models-tosca
+// models-tosca/src/main/java/org/onap/policy/models/tosca/authorative/concepts/ToscaConceptIdentifier.java
+package model
+
+import (
+ "fmt"
+)
+
+type ToscaConceptIdentifier struct {
+ Name string
+ Version string
+}
+
+func NewToscaConceptIdentifier(name, version string) *ToscaConceptIdentifier {
+ return &ToscaConceptIdentifier{
+ Name: name,
+ Version: version,
+ }
+}
+
+func NewToscaConceptIdentifierFromKey(key PfKey) *ToscaConceptIdentifier {
+ return &ToscaConceptIdentifier{
+ Name: key.Name,
+ Version: key.Version,
+ }
+}
+
+func (id *ToscaConceptIdentifier) ValidatePapRest() error {
+ if id.Name == "" || id.Version == "" {
+ return fmt.Errorf("name and version must be non-empty")
+ }
+ return nil
+}
+
+type PfKey struct {
+ Name string
+ Version string
+}
diff --git a/pkg/model/toscaconceptidentifier_test.go b/pkg/model/toscaconceptidentifier_test.go
new file mode 100644
index 0000000..a131483
--- /dev/null
+++ b/pkg/model/toscaconceptidentifier_test.go
@@ -0,0 +1,106 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 model
+
+import (
+ "testing"
+)
+
+// Positive test for NewToscaConceptIdentifier
+func TestNewToscaConceptIdentifier_Success(t *testing.T) {
+ name := "ExampleName"
+ version := "1.0.0"
+ id := NewToscaConceptIdentifier(name, version)
+
+ if id.Name != name {
+ t.Errorf("Expected Name: %s, got: %s", name, id.Name)
+ }
+ if id.Version != version {
+ t.Errorf("Expected Version: %s, got: %s", version, id.Version)
+ }
+}
+
+// Negative test for NewToscaConceptIdentifier with empty name and version
+func TestNewToscaConceptIdentifier_Failure(t *testing.T) {
+ name := ""
+ version := ""
+ id := NewToscaConceptIdentifier(name, version)
+
+ if id.Name != name {
+ t.Errorf("Expected Name to be empty, got: %s", id.Name)
+ }
+ if id.Version != version {
+ t.Errorf("Expected Version to be empty, got: %s", id.Version)
+ }
+}
+
+// Positive test for NewToscaConceptIdentifierFromKey
+func TestNewToscaConceptIdentifierFromKey_Success(t *testing.T) {
+ key := PfKey{Name: "KeyName", Version: "1.0.0"}
+ id := NewToscaConceptIdentifierFromKey(key)
+
+ if id.Name != key.Name {
+ t.Errorf("Expected Name: %s, got: %s", key.Name, id.Name)
+ }
+ if id.Version != key.Version {
+ t.Errorf("Expected Version: %s, got: %s", key.Version, id.Version)
+ }
+}
+
+// Negative test for NewToscaConceptIdentifierFromKey with empty PfKey values
+func TestNewToscaConceptIdentifierFromKey_Failure(t *testing.T) {
+ key := PfKey{Name: "", Version: ""}
+ id := NewToscaConceptIdentifierFromKey(key)
+
+ if id.Name != key.Name {
+ t.Errorf("Expected Name to be empty, got: %s", id.Name)
+ }
+ if id.Version != key.Version {
+ t.Errorf("Expected Version to be empty, got: %s", id.Version)
+ }
+}
+
+// Positive test for ToscaConceptIdentifier.ValidatePapRest
+func TestToscaConceptIdentifier_ValidatePapRest_Success(t *testing.T) {
+ id := NewToscaConceptIdentifier("ValidName", "1.0.0")
+ err := id.ValidatePapRest()
+
+ if err != nil {
+ t.Errorf("Expected no error, got: %v", err)
+ }
+}
+
+// Negative test for ToscaConceptIdentifier.ValidatePapRest with invalid values
+func TestToscaConceptIdentifier_ValidatePapRest_Failure(t *testing.T) {
+ tests := []struct {
+ id *ToscaConceptIdentifier
+ expectErr bool
+ }{
+ {NewToscaConceptIdentifier("", "1.0"), true}, // Missing name
+ {NewToscaConceptIdentifier("ValidName", ""), true}, // Missing version
+ {NewToscaConceptIdentifier("", ""), true}, // Missing name and version
+ }
+
+ for _, test := range tests {
+ err := test.id.ValidatePapRest()
+ if (err != nil) != test.expectErr {
+ t.Errorf("ValidatePapRest() for id: %+v, got error = %v, expectErr = %v", test.id, err != nil, test.expectErr)
+ }
+ }
+}
diff --git a/pkg/opasdk/opasdk.go b/pkg/opasdk/opasdk.go
new file mode 100644
index 0000000..da6c7cc
--- /dev/null
+++ b/pkg/opasdk/opasdk.go
@@ -0,0 +1,92 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// The opasdk package provides functionalities for integrating with the Open Policy Agent
+// (OPA) SDK, including reading configurations and managing a singleton OPA instance.
+// This package is designed to ensure efficient, thread-safe initialization and configuration
+// of the OPA instance.
+package opasdk
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "io"
+ "os"
+ "policy-opa-pdp/consts"
+ "policy-opa-pdp/pkg/log"
+ "sync"
+
+ "github.com/open-policy-agent/opa/sdk"
+)
+
+// Define the structs
+var (
+ opaInstance *sdk.OPA //A singleton instance of the OPA object
+ once sync.Once //A sync.Once variable used to ensure that the OPA instance is initialized only once,
+)
+
+// reads JSON configuration from a file and return a jsonReader
+func getJSONReader(filePath string, openFunc func(string) (*os.File, error),
+ readAllFunc func(io.Reader) ([]byte, error)) (*bytes.Reader, error) {
+ file, err := openFunc(filePath)
+ if err != nil {
+ return nil, fmt.Errorf("error opening file: %w", err)
+ }
+ defer file.Close()
+
+ byteValue, err := readAllFunc(file)
+ if err != nil {
+ return nil, fmt.Errorf("error reading config file: %w", err)
+ }
+
+ jsonReader := bytes.NewReader(byteValue)
+ return jsonReader, nil
+}
+
+// Returns a singleton instance of the OPA object. The initialization of the instance is
+// thread-safe, and the OPA object is configured using a JSON configuration file.
+func GetOPASingletonInstance() (*sdk.OPA, error) {
+ var err error
+ once.Do(func() {
+ var opaErr error
+ opaInstance, opaErr = sdk.New(context.Background(), sdk.Options{
+ // Configure your OPA instance here
+ V1Compatible: true,
+ })
+ log.Debugf("Create an instance of OPA Object")
+ if opaErr != nil {
+ log.Warnf("Error creating OPA instance: %s", opaErr)
+ err = opaErr
+ return
+ } else {
+ jsonReader, jsonErr := getJSONReader(consts.OpasdkConfigPath, os.Open, io.ReadAll)
+ if jsonErr != nil {
+ log.Warnf("Error getting JSON reader: %s", jsonErr)
+ err = jsonErr
+ return
+ }
+ log.Debugf("Configure an instance of OPA Object")
+
+ opaInstance.Configure(context.Background(), sdk.ConfigOptions{
+ Config: jsonReader,
+ })
+ }
+ })
+
+ return opaInstance, err
+}
diff --git a/pkg/opasdk/opasdk_test.go b/pkg/opasdk/opasdk_test.go
new file mode 100644
index 0000000..b6c205b
--- /dev/null
+++ b/pkg/opasdk/opasdk_test.go
@@ -0,0 +1,111 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 opasdk
+
+import (
+ "io"
+ "os"
+ "policy-opa-pdp/consts"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+)
+
+func TestGetOPASingletonInstance_ConfigurationFileNotexisting(t *testing.T) {
+ consts.OpasdkConfigPath = "/app/config/config.json"
+ opaInstance, err := GetOPASingletonInstance()
+ assert.NotNil(t, err) //error no such file or directory /app/config/config.json
+ assert.NotNil(t, opaInstance)
+}
+
+func TestGetOPASingletonInstance_SingletonBehavior(t *testing.T) {
+ tmpFile, err := os.CreateTemp("", "config.json")
+ if err != nil {
+ t.Fatalf("Failed to create temp file: %v", err)
+ }
+ defer os.Remove(tmpFile.Name())
+
+ consts.OpasdkConfigPath = tmpFile.Name()
+
+ // Call the function multiple times
+ opaInstance1, err1 := GetOPASingletonInstance()
+ opaInstance2, err2 := GetOPASingletonInstance()
+
+ // Assertions
+ assert.Nil(t, err1)
+ assert.Nil(t, err2)
+ assert.NotNil(t, opaInstance1)
+ assert.NotNil(t, opaInstance2)
+ assert.Equal(t, opaInstance1, opaInstance2) // Ensure it's the same instance
+}
+
+func TestGetOPASingletonInstance_OPAInstanceCreation(t *testing.T) {
+ tmpFile, err := os.CreateTemp("", "config.json")
+ if err != nil {
+ t.Fatalf("Failed to create temp file: %v", err)
+ }
+ defer os.Remove(tmpFile.Name())
+
+ consts.OpasdkConfigPath = tmpFile.Name()
+
+ // Call the function
+ opaInstance, err := GetOPASingletonInstance()
+
+ // Assertions
+ assert.Nil(t, err)
+ assert.NotNil(t, opaInstance)
+}
+
+// Mock for os.Open
+type MockFile struct {
+ mock.Mock
+}
+
+func (m *MockFile) Open(name string) (*os.File, error) {
+ args := m.Called(name)
+ return args.Get(0).(*os.File), args.Error(1)
+}
+
+// Mock for io.ReadAll
+func mockReadAll(r io.Reader) ([]byte, error) {
+ return []byte(`{"config": "test"}`), nil
+}
+
+func TestGetJSONReader(t *testing.T) {
+ // Create a mock file
+ mockFile := new(MockFile)
+ mockFile.On("Open", "/app/config/config.json").Return(&os.File{}, nil)
+
+ // Call the function with mock functions
+ jsonReader, err := getJSONReader("/app/config/config.json", mockFile.Open, mockReadAll)
+
+ // Check the results
+ assert.NoError(t, err)
+ assert.NotNil(t, jsonReader)
+
+ // Check the content of the jsonReader
+ expectedContent := `{"config": "test"}`
+ actualContent := make([]byte, len(expectedContent))
+ jsonReader.Read(actualContent)
+ assert.Equal(t, expectedContent, string(actualContent))
+
+ // Assert that the mock methods were called
+ mockFile.AssertCalled(t, "Open", "/app/config/config.json")
+}
diff --git a/pkg/pdpattributes/pdpattributes.go b/pkg/pdpattributes/pdpattributes.go
new file mode 100644
index 0000000..70744fd
--- /dev/null
+++ b/pkg/pdpattributes/pdpattributes.go
@@ -0,0 +1,63 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// The pdpattributes package provides utilities for managing and configuring attributes related to the
+// Policy Decision Point (PDP). This includes generating unique PDP names, and setting or retrieving
+// subgroup and heartbeat interval values.
+package pdpattributes
+
+import (
+ "github.com/google/uuid"
+ "policy-opa-pdp/pkg/log"
+)
+
+var (
+ PdpName string // A unique identifier for the PDP instance
+ PdpSubgroup string
+ PdpHeartbeatInterval int64 // The interval (in seconds) at which the PDP sends heartbeat signals
+)
+
+func init() {
+ PdpName = GenerateUniquePdpName()
+ log.Debugf("Name: %s", PdpName)
+}
+
+// Generates a unique PDP name by appending a randomly generated UUID
+func GenerateUniquePdpName() string {
+ return "opa-" + uuid.New().String()
+}
+
+// sets the Pdp Subgroup retrieved from the message from Pap
+func SetPdpSubgroup(pdpsubgroup string) {
+ PdpSubgroup = pdpsubgroup
+}
+
+// Retrieves the current PDP subgroup value.
+func GetPdpSubgroup() string {
+ return PdpSubgroup
+}
+
+// sets the PdpHeratbeatInterval retrieved from the message from Pap
+func SetPdpHeartbeatInterval(pdpHeartbeatInterval int64) {
+ PdpHeartbeatInterval = pdpHeartbeatInterval
+}
+
+// Retrieves the current PDP heartbeat interval value.
+func GetPdpHeartbeatInterval() int64 {
+ return PdpHeartbeatInterval
+
+}
diff --git a/pkg/pdpattributes/pdpattributes_test.go b/pkg/pdpattributes/pdpattributes_test.go
new file mode 100644
index 0000000..0870ed6
--- /dev/null
+++ b/pkg/pdpattributes/pdpattributes_test.go
@@ -0,0 +1,87 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 pdpattributes
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGenerateUniquePdpName_Success(t *testing.T) {
+ t.Run("GenerateValidPdpName", func(t *testing.T) {
+ pdpName := GenerateUniquePdpName()
+ assert.Contains(t, pdpName, "opa-", "Expected PDP name to start with 'opa-'")
+ })
+}
+
+func TestGenerateUniquePdpName_Failure(t *testing.T) {
+ t.Run("UniqueNamesCheck", func(t *testing.T) {
+ pdpName1 := GenerateUniquePdpName()
+ pdpName2 := GenerateUniquePdpName()
+ assert.NotEqual(t, pdpName1, pdpName2, "Expected different UUID for each generated PDP name")
+ assert.Len(t, pdpName1, len("opa-")+36, "Expected length of PDP name to match 'opa-<UUID>' format")
+ })
+}
+
+func TestSetPdpSubgroup_Success(t *testing.T) {
+ t.Run("ValidSubgroup", func(t *testing.T) {
+ expectedSubgroup := "subgroup1"
+ SetPdpSubgroup(expectedSubgroup)
+ assert.Equal(t, expectedSubgroup, GetPdpSubgroup(), "Expected PDP subgroup to match set value")
+ })
+}
+
+func TestSetPdpSubgroup_Failure(t *testing.T) {
+ t.Run("EmptySubgroup", func(t *testing.T) {
+ SetPdpSubgroup("")
+ assert.Equal(t, "", GetPdpSubgroup(), "Expected PDP subgroup to be empty when set to empty string")
+ })
+
+ t.Run("LargeSubgroup", func(t *testing.T) {
+ largeSubgroup := make([]byte, 1024*1024) // 1MB of 'a' characters
+ for i := range largeSubgroup {
+ largeSubgroup[i] = 'a'
+ }
+ SetPdpSubgroup(string(largeSubgroup))
+ assert.Equal(t, string(largeSubgroup), GetPdpSubgroup(), "Expected large PDP subgroup to match set value")
+ })
+}
+
+func TestSetPdpHeartbeatInterval_Success(t *testing.T) {
+ t.Run("ValidHeartbeatInterval", func(t *testing.T) {
+ expectedInterval := int64(30)
+ SetPdpHeartbeatInterval(expectedInterval)
+ assert.Equal(t, expectedInterval, GetPdpHeartbeatInterval(), "Expected heartbeat interval to match set value")
+ })
+}
+
+func TestSetPdpHeartbeatInterval_Failure(t *testing.T) {
+ t.Run("FailureHeartbeatInterval", func(t *testing.T) {
+ SetPdpHeartbeatInterval(-10)
+ assert.Equal(t, int64(-10), GetPdpHeartbeatInterval(), "Expected heartbeat interval to handle negative values")
+ })
+
+ t.Run("LargeHeartbeatInterval", func(t *testing.T) {
+ largeInterval := int64(time.Hour * 24 * 365 * 10) // 10 years in seconds
+ SetPdpHeartbeatInterval(largeInterval)
+ assert.Equal(t, largeInterval, GetPdpHeartbeatInterval(), "Expected PDP heartbeat interval to handle large values")
+ })
+}
diff --git a/pkg/pdpstate/pdpstate.go b/pkg/pdpstate/pdpstate.go
new file mode 100644
index 0000000..0adaa2e
--- /dev/null
+++ b/pkg/pdpstate/pdpstate.go
@@ -0,0 +1,45 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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===================================
+
+// The pdpstate package manages the state of the Policy Decision Point (PDP), allowing for dynamic updates
+// and retrieval of the PDP's current operational state. States are represented using the model.PdpState type.
+package pdpstate
+
+import (
+ "policy-opa-pdp/pkg/model"
+)
+
+var (
+ State model.PdpState = model.Passive // The current state of the PDP.
+ GetCurrentState = GetState // An alias for the GetState function.
+)
+
+// sets the Pdp State retrieved from the message from Pap
+func SetState(stringState string) error {
+ newState, err := model.ConvertStringToEnumState(stringState)
+ if err != nil {
+ return err
+ }
+
+ State = newState
+ return nil
+}
+
+// Retrieves the current PDP state.
+func GetState() model.PdpState {
+ return State
+}
diff --git a/pkg/pdpstate/pdpstate_test.go b/pkg/pdpstate/pdpstate_test.go
new file mode 100644
index 0000000..6b7078c
--- /dev/null
+++ b/pkg/pdpstate/pdpstate_test.go
@@ -0,0 +1,43 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 pdpstate
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "policy-opa-pdp/pkg/model"
+)
+
+func TestSetState_Success(t *testing.T) {
+ t.Run("ValidState", func(t *testing.T) {
+ err := SetState("ACTIVE")
+ assert.NoError(t, err, "Expected no error for valid state")
+ assert.Equal(t, model.Active, GetState(), "Expected state to be set to Active")
+ })
+}
+
+func TestSetState_Failure(t *testing.T) {
+ State = model.Passive
+ t.Run("InvalidState", func(t *testing.T) {
+ err := SetState("InvalidState")
+ assert.Error(t, err, "Expected an error for invalid state")
+ assert.Equal(t, model.Passive, GetState(), "Expected state to remain unchanged when setting invalid state")
+ })
+}
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
new file mode 100644
index 0000000..c2cb591
--- /dev/null
+++ b/pkg/utils/utils.go
@@ -0,0 +1,30 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 utils provides common functionalities
+
+package utils
+
+import (
+ "github.com/google/uuid"
+)
+
+// validates if the given request is in valid uuid form
+func IsValidUUID(u string) bool {
+ _, err := uuid.Parse(u)
+ return err == nil
+}
diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go
new file mode 100644
index 0000000..b70fa2b
--- /dev/null
+++ b/pkg/utils/utils_test.go
@@ -0,0 +1,59 @@
+// -
+// ========================LICENSE_START=================================
+// Copyright (C) 2024: Deutsche Telecom
+//
+// 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 utils
+
+import (
+ "github.com/google/uuid"
+ "testing"
+)
+
+// Positive Test Case: Valid UUIDs
+func TestIsValidUUIDPositive(t *testing.T) {
+ // Define valid UUID strings
+ validUUIDs := []string{
+ "123e4567-e89b-12d3-a456-426614174000", // Standard UUID
+ uuid.New().String(), // Dynamically generated UUID
+ }
+
+ for _, u := range validUUIDs {
+ t.Run("Valid UUID", func(t *testing.T) {
+ if !IsValidUUID(u) {
+ t.Errorf("Expected valid UUID, but got invalid for %s", u)
+ }
+ })
+ }
+}
+
+// Negative Test Case: Invalid UUIDs
+func TestIsValidUUIDNegative(t *testing.T) {
+ // Define invalid UUID strings
+ invalidUUIDs := []string{
+ "123e4567-e89b-12d3-a456-42661417400", // Invalid: missing character at the end
+ "invalid-uuid-format", // Invalid: incorrect format
+ "123e4567-e89b-12d3-a456-42661417400x", // Invalid: contains extra non-hex character
+ " ", // Invalid: empty string
+ }
+
+ for _, u := range invalidUUIDs {
+ t.Run("Invalid UUID", func(t *testing.T) {
+ if IsValidUUID(u) {
+ t.Errorf("Expected invalid UUID, but got valid for %s", u)
+ }
+ })
+ }
+}
diff --git a/test/Opagroup.json b/test/Opagroup.json
new file mode 100644
index 0000000..002b962
--- /dev/null
+++ b/test/Opagroup.json
@@ -0,0 +1,23 @@
+{
+ "groups": [
+ {
+ "name": "defaultGroup",
+ "pdpGroupState": "ACTIVE",
+ "properties": {},
+ "pdpSubgroups": [
+ {
+ "pdpType": "opa",
+ "desiredInstanceCount": 1,
+ "properties": {},
+ "supportedPolicyTypes": [
+ {
+ "name": "onap.policies.native.opa",
+ "version": "1.0.0"
+ }
+ ],
+ "policies": []
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/README.md b/test/README.md
new file mode 100644
index 0000000..7940342
--- /dev/null
+++ b/test/README.md
@@ -0,0 +1,54 @@
+# Testing OPA
+
+## Verification API Calls
+
+curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"example/allow","input":{"method":"POST","path":["users"]}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision
+
+curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z", "policyName":"role/allow","input":{"user":"alice","action":"write","object":"id123","type":"dog"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision
+
+## PERMIT for policy:action
+
+curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"action/allow","input":{"user":"alice","action":"delete","type":"server"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision
+
+{"decision":"PERMIT","policyName":"action/allow","statusMessage":"OPA Allowed"}
+
+## DENY for policy:action
+
+curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"action/allow","input":{"user":"charlie","action":"delete","type":"server"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision
+
+{"decision":"DENY","policyName":"action/allow","statusMessage":"OPA Denied"}
+
+## PERMIT for policy:account
+
+curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC","timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"account/allow", "input":{"creditor_account":11111,"creditor":"alice","debtor_account":22222,"debtor":"bob","period":30,"amount":1000}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision
+
+{"decision":"PERMIT","policyName":"account/allow","statusMessage":"OPA Allowed"}
+
+## DENY for policy:account
+
+curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"account/allow", "input":{"creditor_account":11111,"creditor":"alice","debtor_account":22222,"debtor":"bob","period":31,"amount":1000}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision
+
+{"decision":"DENY","policyName":"account/allow","statusMessage":"OPA Denied"}
+
+## PERMIT for policy:organization
+
+curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"organization/allow", "input":{"user":"alice","action": "read","component": "component_A","project": "project_A", "organization": "org_A"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision
+{"decision":"PERMIT","policyName":"organization/allow","statusMessage":"OPA Allowed"}
+
+## DENY for policy:organization
+
+curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"organization/allow", "input":{"user":"charlie","action": "edit","component": "component_A","project": "project_A", "organization": "org_A"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision
+
+{"decision":"DENY","policyName":"organization/allow","statusMessage":"OPA Denied"}
+
+## HealthCheck API Call With Response
+
+curl -u 'policyadmin:zb!XztG34' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -X GET http://0.0.0.0:8282/policy/pdpx/v1/healthcheck
+
+{"code":200,"healthy":true,"message":"alive","name":"opa-9f0248ea-807e-45f6-8e0f-935e570b75cc","url":"self"}
+
+## Statistics API Call With Response
+
+curl -u 'policyadmin:zb!XztG34' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -X GET http://0.0.0.0:8282/policy/pdpx/v1/statistics
+
+{"code":200,"denyDecisionsCount":10,"deployFailureCount":0,"deploySuccessCount":0,"indeterminantDecisionsCount":0,"permitDecisionsCount":18,"totalErrorCount":4,"totalPoliciesCount":0,"totalPolicyTypesCount":1,"undeployFailureCount":0,"undeploySuccessCount":0}
diff --git a/test/config.json b/test/config.json
new file mode 100644
index 0000000..3f2aa43
--- /dev/null
+++ b/test/config.json
@@ -0,0 +1,24 @@
+{
+ "logging": {
+ "level": "debug"
+ },
+ "services": [
+ {
+ "name": "opa-bundle-server",
+ "url": "http://localhost:8282/opa/bundles"
+ }
+ ],
+ "bundles": {
+ "opabundle": {
+ "service": "opa-bundle-server",
+ "resource": "bundle.tar.gz",
+ "polling": {
+ "min_delay_seconds": 60,
+ "max_delay_seconds": 120
+ }
+ }
+ },
+ "decision_logs": {
+ "console": true
+ }
+}
diff --git a/test/config/opa-pdp/config.json b/test/config/opa-pdp/config.json
new file mode 100644
index 0000000..3f2aa43
--- /dev/null
+++ b/test/config/opa-pdp/config.json
@@ -0,0 +1,24 @@
+{
+ "logging": {
+ "level": "debug"
+ },
+ "services": [
+ {
+ "name": "opa-bundle-server",
+ "url": "http://localhost:8282/opa/bundles"
+ }
+ ],
+ "bundles": {
+ "opabundle": {
+ "service": "opa-bundle-server",
+ "resource": "bundle.tar.gz",
+ "polling": {
+ "min_delay_seconds": 60,
+ "max_delay_seconds": 120
+ }
+ }
+ },
+ "decision_logs": {
+ "console": true
+ }
+}
diff --git a/test/config/opa-pdp/groups.json b/test/config/opa-pdp/groups.json
new file mode 100644
index 0000000..502700c
--- /dev/null
+++ b/test/config/opa-pdp/groups.json
@@ -0,0 +1,15 @@
+{
+ "groups": [
+ {
+ "name": "defaultGroup",
+ "version": "1.0.0",
+ "description": "The default group that registers all supported policy types and pdps.",
+ "pdpGroupState": "ACTIVE",
+ "pdpSubgroups": [
+ {
+ "pdpType": "opa"
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/config/opa-pdp/policy-opa-pdp.sh b/test/config/opa-pdp/policy-opa-pdp.sh
new file mode 100755
index 0000000..7ed14cb
--- /dev/null
+++ b/test/config/opa-pdp/policy-opa-pdp.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+
+#Creation of Policies and Groups
+sh scripts.sh
+
+
+#Execution of OPA-PDP bin
+/app/opa-pdp
diff --git a/test/docker-compose.yml b/test/docker-compose.yml
new file mode 100644
index 0000000..6778882
--- /dev/null
+++ b/test/docker-compose.yml
@@ -0,0 +1,143 @@
+version: '3.8'
+services:
+ mariadb:
+ image: nexus3.onap.org:10001/mariadb:10.10.2
+ container_name: mariadb
+ hostname: mariadb
+ command: ['--lower-case-table-names=1', '--wait_timeout=28800', '--default-authentication-plugin=mysql_native_password']
+ env_file: ./config/db/db.conf
+ volumes:
+ - ./config/db:/docker-entrypoint-initdb.d
+ - ./config/clamp/policy-clamp-create-tables.sql:/tmp/policy-clamp-create-tables.sql
+ ports:
+ - "3306:3306"
+ policy-db-migrator:
+ image: nexus3.onap.org:10001/onap/policy-db-migrator:4.0.1-SNAPSHOT
+ container_name: policy-db-migrator
+ hostname: policy-db-migrator
+ depends_on:
+ - mariadb
+ expose:
+ - 6824
+ env_file: ./config/db/db.conf
+ environment:
+ SQL_DB: policyadmin
+ SQL_HOST: mariadb
+ volumes:
+ - ./config/db-migrator/init.sh:/opt/app/policy/bin/db_migrator_policy_init.sh:ro
+ - ./wait_for_port.sh:/tmp/wait_for_port.sh
+ entrypoint: sh /tmp/wait_for_port.sh
+ command: [
+ '-c',
+ '/opt/app/policy/bin/db_migrator_policy_init.sh',
+ 'mariadb', '3306'
+ ]
+ api:
+ image: nexus3.onap.org:10001/onap/policy-api:4.0.1-SNAPSHOT
+ container_name: policy-api
+ depends_on:
+ - policy-db-migrator
+ hostname: policy-api
+ ports:
+ - 30002:6969
+ volumes:
+ - ./config/api/apiParameters.yaml:/opt/app/policy/api/etc/apiParameters.yaml:ro
+ - ./config/api/logback.xml:/opt/app/policy/api/etc/logback.xml:ro
+ - ./wait_for_port.sh:/opt/app/policy/api/bin/wait_for_port.sh
+ entrypoint: sh wait_for_port.sh
+ command: [
+ '-c', './policy-api.sh',
+ 'mariadb', '3306',
+ 'policy-db-migrator', '6824'
+ ]
+ pap:
+ image: nexus3.onap.org:10001/onap/policy-pap:4.0.1-SNAPSHOT
+ container_name: policy-pap
+ depends_on:
+ - mariadb
+ - kafka
+ - api
+ hostname: policy-pap
+ ports:
+ - 30003:6969
+ volumes:
+ - ./config/pap/papParameters.yaml:/opt/app/policy/pap/etc/papParameters.yaml:ro
+ - ./config/pap/groups.json:/opt/app/policy/pap/etc/mounted/groups.json:ro
+ - ./config/pap/logback.xml:/opt/app/policy/pap/etc/logback.xml:ro
+ - ./wait_for_port.sh:/opt/app/policy/pap/bin/wait_for_port.sh
+ entrypoint: sh wait_for_port.sh
+ command: [
+ '-c', './policy-pap.sh',
+ 'mariadb', '3306',
+ 'kafka', '9092',
+ 'api', '6969'
+ ]
+ zookeeper:
+ image: confluentinc/cp-zookeeper:latest
+ environment:
+ ZOOKEEPER_CLIENT_PORT: 2181
+ ZOOKEEPER_TICK_TIME: 2000
+ ports:
+ - 2181:2181
+
+ pdp:
+ image: docker.io/opa-pdp:1.0.0
+ container_name: opa-pdp
+ depends_on:
+ - mariadb
+ - kafka
+ - api
+ - pap
+ hostname: opa-pdp
+ volumes:
+ - ./config/opa-pdp/config.json:/app/config/config.json:ro
+ - ./config/opa-pdp/groups.json:/app/groups.json:ro
+ - ./config/opa-pdp/policy-opa-pdp.sh:/app/policy-opa-pdp.sh:ro
+ - ./wait_for_port.sh:/app/wait_for_port.sh
+ - ./scripts.sh:/app/scripts.sh
+ - ./Opagroup.json:/app/Opagroup.json
+ - ./policy-new.yaml:/app/policy-new.yaml
+ - type: bind
+ source: ./policies
+ target: /app/policies
+
+ environment:
+ LOG_LEVEL: debug
+ KAFKA_URL: "kafka:9092"
+ PAP_TOPIC: policy-pdp-pap
+ GROUPID: opa-pdp
+ API_USER: policyadmin
+ API_PASSWORD: "zb!XztG34"
+ entrypoint: sh wait_for_port.sh
+ command: [
+ '-c', './policy-opa-pdp.sh',
+ 'mariadb', '3306',
+ 'kafka', '9092',
+ 'api', '6969',
+ 'pap', '6969'
+ ]
+ ports:
+ - 8282:8282
+ zookeeper:
+ image: confluentinc/cp-zookeeper:latest
+ environment:
+ ZOOKEEPER_CLIENT_PORT: 2181
+ ZOOKEEPER_TICK_TIME: 2000
+ ports:
+ - 2181:2181
+
+ kafka:
+ image: confluentinc/cp-kafka:latest
+ container_name: kafka
+ depends_on:
+ - zookeeper
+ ports:
+ - 29092:29092
+ - 9092:9092
+ environment:
+ KAFKA_BROKER_ID: 1
+ KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
diff --git a/test/policies/abac/policy.rego b/test/policies/abac/policy.rego
new file mode 100644
index 0000000..9dc6ea9
--- /dev/null
+++ b/test/policies/abac/policy.rego
@@ -0,0 +1,20 @@
+package abac
+
+import rego.v1
+
+default allow := false
+
+allow if {
+ viewable_sensor_data
+ action_is_read
+}
+
+action_is_read if "read" in input.actions
+
+viewable_sensor_data contains view_data if {
+ some sensor_data in data.abac.sensor_data
+ sensor_data.timestamp >= input.time_period.from
+ sensor_data.timestamp < input.time_period.to
+
+ view_data := {datatype: sensor_data[datatype] | datatype in input.datatypes}
+}
diff --git a/test/policies/account/policy.rego b/test/policies/account/policy.rego
new file mode 100644
index 0000000..f99e8eb
--- /dev/null
+++ b/test/policies/account/policy.rego
@@ -0,0 +1,17 @@
+package account
+
+import rego.v1
+
+default allow := false
+
+allow if {
+ creditor_is_valid
+ debtor_is_valid
+ period_is_valid
+ amount_is_valid
+}
+creditor_is_valid if data.account.account_attributes[input.creditor_account].owner == input.creditor
+debtor_is_valid if data.account.account_attributes[input.debtor_account].owner == input.debtor
+
+period_is_valid if input.period <= 30
+amount_is_valid if data.account.account_attributes[input.debtor_account].amount >= input.amount
diff --git a/test/policies/action/policy.rego b/test/policies/action/policy.rego
new file mode 100644
index 0000000..300fe50
--- /dev/null
+++ b/test/policies/action/policy.rego
@@ -0,0 +1,21 @@
+package action
+
+import rego.v1
+
+# By default, deny requests.
+default allow := false
+
+
+# Allow the action if admin role is granted permission to perform the action.
+allow if {
+ some i
+ data.action.user_roles[input.user][i] == role
+ some j
+ data.action.role_permissions[role].actions[j] == input.action
+ some k
+ data.action.role_permissions[role].resources[k] == input.type
+}
+# * Rego comparison to other systems: https://www.openpolicyagent.org/docs/latest/comparison-to-other-systems/
+# * Rego Iteration: https://www.openpolicyagent.org/docs/latest/#iteration
+
+
diff --git a/test/policies/data/abac/data.json b/test/policies/data/abac/data.json
new file mode 100644
index 0000000..77b5668
--- /dev/null
+++ b/test/policies/data/abac/data.json
@@ -0,0 +1,94 @@
+{
+ "sensor_data": [
+ {
+ "id": "0001",
+ "location": "Sri Lanka",
+ "temperature": "28 C",
+ "precipitation": "1000 mm",
+ "windspeed": "5.5 m/s",
+ "humidity": "40%",
+ "particle_density": "1.3 g/l",
+ "timestamp": "2024-02-26"
+ },
+ {
+ "id": "0002",
+ "location": "Colombo",
+ "temperature": "30 C",
+ "precipitation": "1200 mm",
+ "windspeed": "6.0 m/s",
+ "humidity": "45%",
+ "particle_density": "1.5 g/l",
+ "timestamp": "2024-02-26"
+ },
+ {
+ "id": "0003",
+ "location": "Kandy",
+ "temperature": "25 C",
+ "precipitation": "800 mm",
+ "windspeed": "4.5 m/s",
+ "humidity": "60%",
+ "particle_density": "1.1 g/l",
+ "timestamp": "2024-02-26"
+ },
+ {
+ "id": "0004",
+ "location": "Galle",
+ "temperature": "35 C",
+ "precipitation": "500 mm",
+ "windspeed": "7.2 m/s",
+ "humidity": "30%",
+ "particle_density": "1.8 g/l",
+ "timestamp": "2024-02-27"
+ },
+ {
+ "id": "0005",
+ "location": "Jaffna",
+ "temperature": "-5 C",
+ "precipitation": "300 mm",
+ "windspeed": "3.8 m/s",
+ "humidity": "20%",
+ "particle_density": "0.9 g/l",
+ "timestamp": "2024-02-27"
+ },
+ {
+ "id": "0006",
+ "location": "Trincomalee",
+ "temperature": "20 C",
+ "precipitation": "1000 mm",
+ "windspeed": "5.0 m/s",
+ "humidity": "55%",
+ "particle_density": "1.2 g/l",
+ "timestamp": "2024-02-28"
+ },
+ {
+ "id": "0007",
+ "location": "Nuwara Eliya",
+ "temperature": "25 C",
+ "precipitation": "600 mm",
+ "windspeed": "4.0 m/s",
+ "humidity": "50%",
+ "particle_density": "1.3 g/l",
+ "timestamp": "2024-02-28"
+ },
+ {
+ "id": "0008",
+ "location": "Anuradhapura",
+ "temperature": "28 C",
+ "precipitation": "700 mm",
+ "windspeed": "5.8 m/s",
+ "humidity": "40%",
+ "particle_density": "1.4 g/l",
+ "timestamp": "2024-02-29"
+ },
+ {
+ "id": "0009",
+ "location": "Matara",
+ "temperature": "32 C",
+ "precipitation": "900 mm",
+ "windspeed": "6.5 m/s",
+ "humidity": "65%",
+ "particle_density": "1.6 g/l",
+ "timestamp": "2024-02-29"
+ }
+ ]
+}
diff --git a/test/policies/data/account/data.json b/test/policies/data/account/data.json
new file mode 100644
index 0000000..df263d3
--- /dev/null
+++ b/test/policies/data/account/data.json
@@ -0,0 +1,16 @@
+{
+ "account_attributes":{
+ "11111":{
+ "owner":"alice",
+ "amount":10000
+ },
+ "22222":{
+ "owner":"bob",
+ "amount":10000
+ },
+ "33333":{
+ "owner":"cam",
+ "amount":10000
+ }
+ }
+}
diff --git a/test/policies/data/action/data.json b/test/policies/data/action/data.json
new file mode 100644
index 0000000..99145b7
--- /dev/null
+++ b/test/policies/data/action/data.json
@@ -0,0 +1,43 @@
+{
+ "user_roles": {
+ "alice": [
+ "admin"
+ ],
+ "bob": [
+ "editor"
+ ],
+ "charlie": [
+ "viewer"
+ ]
+ },
+ "role_permissions": {
+ "admin": {
+ "actions": [
+ "read",
+ "write",
+ "delete"
+ ],
+ "resources": [
+ "server",
+ "database"
+ ]
+ },
+ "editor": {
+ "actions": [
+ "read",
+ "write"
+ ],
+ "resources": [
+ "server"
+ ]
+ },
+ "viewer": {
+ "actions": [
+ "read"
+ ],
+ "resources": [
+ "server"
+ ]
+ }
+ }
+}
diff --git a/test/policies/data/organization/data.json b/test/policies/data/organization/data.json
new file mode 100644
index 0000000..35fe4a1
--- /dev/null
+++ b/test/policies/data/organization/data.json
@@ -0,0 +1,32 @@
+{
+ "acls": [
+ {
+ "user": "alice",
+ "actions": [
+ "edit",
+ "read"
+ ],
+ "component": "component_A",
+ "project": "project_A",
+ "organization": "org_A"
+ },
+ {
+ "user": "bob",
+ "actions": ["read"],
+ "organization": "org_A"
+ },
+ {
+ "user": "bob",
+ "action": ["edit"],
+ "component": "component_A",
+ "project": "project_B",
+ "organization": "org_A"
+ },
+ {
+ "user": "charlie",
+ "action": ["read"],
+ "project": "project_B",
+ "organization": "org_A"
+ }
+ ]
+}
diff --git a/test/policies/data/role/data.json b/test/policies/data/role/data.json
new file mode 100644
index 0000000..88ac41b
--- /dev/null
+++ b/test/policies/data/role/data.json
@@ -0,0 +1,63 @@
+{
+ "user_roles": {
+ "alice": [
+ "admin"
+ ],
+ "bob": [
+ "employee",
+ "billing"
+ ],
+ "eve": [
+ "customer"
+ ]
+ },
+ "role_grants": {
+ "customer": [
+ {
+ "action": "read",
+ "type": "dog"
+ },
+ {
+ "action": "read",
+ "type": "cat"
+ },
+ {
+ "action": "adopt",
+ "type": "dog"
+ },
+ {
+ "action": "adopt",
+ "type": "cat"
+ }
+ ],
+ "employee": [
+ {
+ "action": "read",
+ "type": "dog"
+ },
+ {
+ "action": "read",
+ "type": "cat"
+ },
+ {
+ "action": "update",
+ "type": "dog"
+ },
+ {
+ "action": "update",
+ "type": "cat"
+ }
+ ],
+ "billing": [
+ {
+ "action": "read",
+ "type": "finance"
+ },
+ {
+ "action": "update",
+ "type": "finance"
+ }
+ ]
+ }
+}
+
diff --git a/test/policies/example/policy.rego b/test/policies/example/policy.rego
new file mode 100644
index 0000000..cc19285
--- /dev/null
+++ b/test/policies/example/policy.rego
@@ -0,0 +1,13 @@
+package example
+
+import rego.v1
+
+allow if {
+ input.path == ["users"]
+ input.method == "POST"
+}
+
+allow if {
+ input.path == ["users", input.user_id]
+ input.method == "GET"
+}
diff --git a/test/policies/organization/policy.rego b/test/policies/organization/policy.rego
new file mode 100644
index 0000000..31e7fb6
--- /dev/null
+++ b/test/policies/organization/policy.rego
@@ -0,0 +1,38 @@
+package organization
+
+import rego.v1
+
+default allow := false
+
+# organization level access
+allow if {
+ some acl in data.organization.acls
+ acl.user == input.user
+ acl.organization == input.organization
+ acl.project == input.project
+ acl.component == input.component
+
+ some action in acl.actions
+ action == input.action
+}
+
+# project level access
+allow if {
+ some acl in data.organization.acls
+ acl.user == input.user
+ acl.organization == input.organization
+ acl.project == input.project
+
+ some action in acl.actions
+ action == input.action
+}
+
+# component level access
+allow if {
+ some acl in data.organization.acls
+ acl.user == input.user
+ acl.organization == input.organization
+
+ some action in acl.actions
+ action == input.action
+}
diff --git a/test/policies/role/policy.rego b/test/policies/role/policy.rego
new file mode 100644
index 0000000..54bdecf
--- /dev/null
+++ b/test/policies/role/policy.rego
@@ -0,0 +1,53 @@
+# Role-based Access Control (RBAC)
+# --------------------------------
+#
+# This example defines an RBAC model for a Pet Store API. The Pet Store API allows
+# users to look at pets, adopt them, update their stats, and so on. The policy
+# controls which users can perform actions on which resources. The policy implements
+# a classic Role-based Access Control model where users are assigned to roles and
+# roles are granted the ability to perform some action(s) on some type of resource.
+#
+# This example shows how to:
+#
+# * Define an RBAC model in Rego that interprets role mappings represented in JSON.
+# * Iterate/search across JSON data structures (e.g., role mappings)
+#
+# For more information see:
+#package app.rbac
+package role
+
+import rego.v1
+
+# By default, deny requests.
+default allow := false
+
+# Allow admins to do anything.
+allow if user_is_admin
+
+# Allow the action if the user is granted permission to perform the action.
+allow if {
+ # Find grants for the user.
+ some grant in user_is_granted
+
+ # Check if the grant permits the action.
+ input.action == grant.action
+ input.type == grant.type
+}
+
+# user_is_admin is true if "admin" is among the user's roles as per data.user_roles
+user_is_admin if "admin" in data.role.user_roles[input.user]
+
+# user_is_granted is a set of grants for the user identified in the request.
+# The `grant` will be contained if the set `user_is_granted` for every...
+user_is_granted contains grant if {
+ # `role` assigned an element of the user_roles for this user...
+ some role in data.role.user_roles[input.user]
+
+ # `grant` assigned a single grant from the grants list for 'role'...
+ some grant in data.role.role_grants[role]
+}
+
+# * Rego comparison to other systems: https://www.openpolicyagent.org/docs/latest/comparison-to-other-systems/
+# * Rego Iteration: https://www.openpolicyagent.org/docs/latest/#iteration
+
+
diff --git a/test/policy-new.yaml b/test/policy-new.yaml
new file mode 100644
index 0000000..2fbcf79
--- /dev/null
+++ b/test/policy-new.yaml
@@ -0,0 +1,21 @@
+tosca_definitions_version: tosca_simple_yaml_1_1_0
+policy_types:
+ onap.policies.Native:
+ derived_from: tosca.policies.Root
+ description: a base policy type for all native PDP policies
+ version: 1.0.0
+ name: onap.policies.Native
+ onap.policies.native.opa:
+ derived_from: onap.policies.Native
+ version: 1.0.0
+ name: onap.policies.native.opa
+ description: a policy type for native opa policies
+ properties:
+ policy:
+ type: string
+ type_version: 0.0.0
+ description: The rego PolicySet or Policy
+ required: true
+ metadata:
+ encoding: Base64
+
diff --git a/test/scripts.sh b/test/scripts.sh
new file mode 100755
index 0000000..ab4f838
--- /dev/null
+++ b/test/scripts.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+# Set up credentials and host variables
+USER="policyadmin"
+PASSWORD="zb!XztG34"
+HOST="localhost"
+
+# Exit immediately if a command exits with a non-zero status
+set -e
+
+# Step 1: Create a Policy
+echo "Creating a new policy..."
+sleep 40
+curl -u "$USER:$PASSWORD" --header "Content-Type: application/yaml" \
+ -X POST --data-binary @policy-new.yaml \
+ http://policy-api:6969/policy/api/v1/policytypes
+echo "Policy created successfully. Check policy-api logs for details."
+
+# Step 2: Create Groups
+echo "Creating groups..."
+curl -u "$USER:$PASSWORD" --header "Content-Type: application/json" \
+ -X POST --data-binary @Opagroup.json \
+ http://policy-pap:6969/policy/pap/v1/pdps/groups/batch
+
+echo "Groups created successfully. Check policy-pap logs for details."
+
+echo "Script execution completed."
diff --git a/test/scripts.txt b/test/scripts.txt
new file mode 100644
index 0000000..3d60d4a
--- /dev/null
+++ b/test/scripts.txt
@@ -0,0 +1,21 @@
+ curl -u 'policyadmin:zb!XztG34' --header "Content-Type:application/yaml" -X POST --data-binary @policy-new.yaml http://localhost:30002/policy/api/v1/policytypes
+
+# policy-new.yaml is inside test directory to create policy
+#check policy-api logs
+
+
+//Create Groups
+
+curl -u 'policyadmin:zb!XztG34' --header "Content-Type:application/json" -X POST --data-binary @Opagroup.json http://localhost:30003/policy/pap/v1/pdps/groups/batch
+
+#Check policy-pap logs
+#file Opagroup.json is inside test
+
+// Sends registration message to policy-pdp-pap
+
+docker exec -it kafka /bin/sh
+
+echo '{"messageName": "PDP_STATUS", "requestId": "e9b4ee77-5400-41a8-87ba-3c914a86ee08", "timestampMs": "1728551661460","name": "opa-2e953ecf-40f1-47f7-8a5e-53031947516c","pdpGroup": "opaGroup","pdpSubgroup": null, "pdpType": "opa","state": "PASSIVE","healthy": "HEALTHY", "description": null, "policies": []}' | kafka-console-producer --broker-list kafka:9092 --topic policy-pdp-pap
+
+
+#To get Gracefulshutdown signals commented command and changed entrypoint to /app/opa-pdp
diff --git a/test/wait_for_port.sh b/test/wait_for_port.sh
new file mode 100644
index 0000000..b29102b
--- /dev/null
+++ b/test/wait_for_port.sh
@@ -0,0 +1,91 @@
+#!/bin/sh
+# ============LICENSE_START====================================================
+# Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+# Modifications Copyright (C) 2022-2023 Nordix Foundation.
+# =============================================================================
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END======================================================
+
+usage() {
+ echo args: [-t timeout] [-c command] hostname1 port1 hostname2 port2 ... >&2
+ exit 1
+}
+
+tmout=300
+cmd=
+while getopts c:t: opt
+do
+ case "$opt" in
+ c)
+ cmd="$OPTARG"
+ ;;
+
+ t)
+ tmout="$OPTARG"
+ ;;
+
+ *)
+ usage
+ ;;
+ esac
+done
+
+nargs=$((OPTIND-1))
+shift "$nargs"
+
+even_args=$(($#%2))
+if [ $# -lt 2 ] || [ "$even_args" -ne 0 ]
+then
+ usage
+fi
+
+while [ $# -ge 2 ]
+do
+ export host="$1"
+ export port="$2"
+ shift
+ shift
+
+ echo "Waiting for $host port $port..."
+
+ while [ "$tmout" -gt 0 ]
+ do
+ if command -v docker > /dev/null 2>&1
+ then
+ docker ps --format "table {{ .Names }}\t{{ .Status }}"
+ fi
+
+ nc -vz "$host" "$port"
+ rc=$?
+
+ if [ $rc -eq 0 ]
+ then
+ break
+ else
+ tmout=$((tmout-1))
+ sleep 1
+ fi
+ done
+
+ if [ $rc -ne 0 ]
+ then
+ echo "$host port $port cannot be reached"
+ exit $rc
+ fi
+done
+#sh scripts.sh
+$cmd
+
+exit 0
diff --git a/version b/version
new file mode 100644
index 0000000..3eefcb9
--- /dev/null
+++ b/version
@@ -0,0 +1 @@
+1.0.0