--- /dev/null
+# Documentation
+.idea/
+.tox
+docs/_build/
+.DS_STORE
+.swagger*
+docs/offeredapis/swagger/README.md
+
+# Eclipse
+.checkstyle
+.classpath
+target/
+.sts4-cache
+.project
+.settings
+.pydevproject
+infer-out/
+
+.vscode
+.factorypath
+
+coverage.*
--- /dev/null
+from docs_conf.conf import *
+
+#branch configuration
+
+branch = 'latest'
+
+linkcheck_ignore = [
+ 'http://localhost.*',
+ 'http://127.0.0.1.*',
+ 'https://gerrit.o-ran-sc.org.*',
+]
+
+extensions = ['sphinxcontrib.redoc', 'sphinx.ext.intersphinx',]
--- /dev/null
+---
+project_cfg: oran
+project: nonrtric
--- /dev/null
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. SPDX-License-Identifier: CC-BY-4.0
+.. Copyright (C) 2021 Nordix
+
+Developer Guide
+===============
+
+This document provides a quickstart for developers of the RAN Slice Assurance use case.
+
+Additional developer guides are available on the `O-RAN SC NONRTRIC Developer wiki <https://wiki.o-ran-sc.org/display/RICNR/Release+E>`_.
+
+RAN Slice Assurance usecase
+---------------------------
+
+See the page in Wiki: `O-DU Slice Assurance usecase <https://wiki.o-ran-sc.org/display/RICNR/O-DU+Slice+Assurance+usecase>`_.
+
+
+Kubernetes deployment
+=====================
+
+Non-RT RIC can be also deployed in a Kubernetes cluster, `it/dep repository <https://gerrit.o-ran-sc.org/r/admin/repos/it/dep>`_.
+hosts deployment and integration artifacts. Instructions and helm charts to deploy the Non-RT-RIC functions in the
+OSC NONRTRIC integrated test environment can be found in the *./nonrtric* directory.
+
+For more information on installation of NonRT-RIC in Kubernetes, see `Deploy NONRTRIC in Kubernetes <https://wiki.o-ran-sc.org/display/RICNR/Deploy+NONRTRIC+in+Kubernetes>`_.
+
+For more information see `Integration and Testing documentation on the O-RAN-SC wiki <https://docs.o-ran-sc.org/projects/o-ran-sc-it-dep/en/latest/index.html>`_.
+
--- /dev/null
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. SPDX-License-Identifier: CC-BY-4.0
+.. Copyright (C) 2021 Nordix
+
+Non-RT RIC
+==========
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
+
+ ./overview.rst
+ ./developer-guide.rst
+ ./release-notes.rst
+
+* :ref:`search`
--- /dev/null
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. SPDX-License-Identifier: CC-BY-4.0
+.. Copyright (C) 2021 Nordix
+
+
+RAN Slice Assurance usecase
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A very simplified closed-loop rApp use case to re-prioritize a RAN slice's radio resource allocation priority if sufficient throughput cannot be maintained. Not intended to to be 'real-world'.
--- /dev/null
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. http://creativecommons.org/licenses/by/4.0
+.. Copyright (C) 2021 Nordix
+
+=============
+Release-Notes
+=============
+
+
+This document provides the release notes for the release of the different parts of the Non-RT RIC.
+
+.. contents::
+ :depth: 1
+ :local:
+
+Version history RAN Slice Assurance usecase
+============================================
+
++------------+----------+------------------+-----------------+
+| **Date** | **Ver.** | **Author** | **Comment** |
+| | | | |
++------------+----------+------------------+-----------------+
+| 2021-12-14 | 1.0.0 | Henrik Andersson | E Release |
+| | | | Initial version |
++------------+----------+------------------+-----------------+
+| 2022-02-14 | 1.0.2 | Henrik Andersson | E Maintenance |
+| | | | Release |
++------------+----------+------------------+-----------------+
+
+
+Release Data
+============
+
+Bronze
+------
++-----------------------------+---------------------------------------------------+
+| **Project** | Non-RT RIC |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Repo/commit-ID** | nonrtric/2466f9d370214b578efedd1d3e38b1de17e6ca1c |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release designation** | Bronze |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release date** | 2020-06-18 |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Purpose of the delivery** | Improved stability |
+| | |
++-----------------------------+---------------------------------------------------+
+
+Bronze Maintenance
+------------------
++-----------------------------+---------------------------------------------------+
+| **Project** | Non-RT RIC |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Repo/commit-ID** | nonrtric/5d4f252a530a0d9abbf2a363354c5e56e8f2f33e |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release designation** | Bronze |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release date** | 2020-07-29 |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Purpose of the delivery** | Introduce configuration of certificates |
+| | |
++-----------------------------+---------------------------------------------------+
+
+Cherry
+------
++-----------------------------+---------------------------------------------------+
+| **Project** | Non-RT RIC |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Repo/commit-ID** | nonrtric/90ce16238dd6970153e1c0fbddb15e32c68c504f |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release designation** | Cherry |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release date** | 2020-12-03 |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Purpose of the delivery** | Introduction of Enrichment Service Coordinator |
+| | and rAPP Catalogue |
+| | |
++-----------------------------+---------------------------------------------------+
+
+D
+-
++-----------------------------+---------------------------------------------------+
+| **Project** | Non-RT RIC |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Repo/commit-ID** | nonrtric/dd3ebfd784e96919a00ddd745826f8a8e074c66f |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release designation** | D |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release date** | 2021-06-23 |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Purpose of the delivery** | Improvements |
+| | Introduction of initial version of Helm Manager |
++-----------------------------+---------------------------------------------------+
+
+D Maintenance
+-------------
++-----------------------------+---------------------------------------------------+
+| **Project** | Non-RT RIC |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Repo/commit-ID** | nonrtric/973ae56894fb29a929fba9e344cae42e7607087b |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release designation** | D |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release date** | 2021-08-10 |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Purpose of the delivery** | Minor bug fixes |
++-----------------------------+---------------------------------------------------+
+
+E Release
+---------
++-----------------------------+---------------------------------------------------+
+| **Project** | Non-RT RIC |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Repo/commit-ID** | nonrtric/b472c167413a55a42fc7bfa08d2138f967a204fb |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release designation** | E |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release date** | 2021-12-13 |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Purpose of the delivery** | Improvements and renaming. |
+| | Introduction of more usecase implementations. |
++-----------------------------+---------------------------------------------------+
+
+E Maintenance Release
+---------------------
++-----------------------------+---------------------------------------------------+
+| **Project** | Non-RT RIC |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Repo/commit-ID** | nonrtric/4df1f9ca4cd1ebc21e0c5ea57bcb0b7ef096d067 |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release designation** | E |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Release date** | 2022-02-09 |
+| | |
++-----------------------------+---------------------------------------------------+
+| **Purpose of the delivery** | Improvements and bug fixes |
+| | |
++-----------------------------+---------------------------------------------------+
+
--- /dev/null
+tox
+sphinx
+sphinxcontrib-swaggerdoc
+sphinx_bootstrap_theme
+sphinxcontrib-redoc
+lfdocs-conf
\ No newline at end of file
--- /dev/null
+#==================================================================================
+# Copyright (C) 2021: 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.
+#
+#==================================================================================
+##
+## Build
+##
+FROM nexus3.o-ran-sc.org:10001/golang:1.17-bullseye AS build
+
+WORKDIR /app
+
+COPY go.mod ./
+COPY go.sum ./
+RUN go mod download
+
+COPY . ./
+
+RUN go build -o /oduclosedloop-sliceassurance
+
+##
+## Deploy
+##
+FROM gcr.io/distroless/base-debian10
+
+WORKDIR /
+
+## Copy from "build" stage
+COPY --from=build /oduclosedloop-sliceassurance .
+
+USER nonroot:nonroot
+
+ENTRYPOINT ["/oduclosedloop-sliceassurance"]
\ No newline at end of file
--- /dev/null
+# Copyright (C) 2022 Nordix Foundation. All rights reserved.
+# ========================================================================
+# 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=================================================
+#
+
+##
+## Build
+##
+FROM golang:1.17.1-bullseye AS build
+
+WORKDIR /app
+
+COPY . ./
+
+RUN go build -o /sdnr-mr-sim ./stub/
+
+##
+## Deploy
+##
+FROM gcr.io/distroless/base-debian10
+
+WORKDIR /
+
+## Copy from "build" stage
+COPY --from=build /sdnr-mr-sim .
+COPY --from=build /app/stub/test-data.csv .
+
+USER nonroot:nonroot
+
+ENTRYPOINT ["/sdnr-mr-sim"]
--- /dev/null
+# O-RAN-SC Non-RealTime RIC O-DU Closed Loop Usecase Slice Assurance
+
+## Configuration
+
+The consumer takes a number of environment variables, described below, as configuration.
+
+>- MR_HOST **Required**. The host for Dmaap Message Router. Example: `http://mrproducer`
+>- MR_PORT **Required**. The port for the Dmaap Message Router. Example: `8095`
+>- SDNR_ADDR Optional. The address for SDNR. Defaults to `http://localhost:3904`.
+>- SDNR_USER Optional. The user for the SDNR. Defaults to `admin`.
+>- SDNR_PASSWORD Optional. The password for the SDNR user. Defaults to `Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U`.
+>- LOG_LEVEL Optional. The log level, which can be `Error`, `Warn`, `Info` or `Debug`. Defaults to `Info`.
+>- POLLTIME Optional. Waiting time between one pull request to Dmaap and another. Defaults to 10 sec
+
+## Functionality
+
+There is a status call provided in a REST API on port 40936.
+>- /status OK
+
+## Development
+
+To make it easy to test during development of the consumer, there is a stub provided in the `stub` folder.
+
+This stub is used to simulate both received VES messages from Dmaap MR with information about performance measurements for the slices in a determinated DU and also SDNR, that sends information about Radio Resource Management Policy Ratio and allows to modify value for RRM Policy Dedicated Ratio from default to higher value.
+
+By default, SDNR stub listens to the port `3904`, but his can be overridden by passing a `--sdnr-port [PORT]` flag when starting the stub. For Dmaap MR stub default port is `3905` but it can be overriden by passing a `--dmaap-port [PORT]` flag when starting the stub.
+
+To build and start the stub, do the following:
+
+>1. cd stub
+>2. go build
+>3. ./stub [--sdnr-port <portNo>] [--dmaap-port <portNo>]
+
+## License
+
+Copyright (C) 2021 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.
+
+For more information about license please see the [LICENSE](LICENSE.txt) file for details.
\ No newline at end of file
--- /dev/null
+#!/bin/bash
+##############################################################################
+#
+# Copyright (C) 2021: 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.
+#
+##############################################################################
+set -eux
+
+echo "--> build-ransliceassurance-ubuntu.sh"
+curdir=`pwd`
+# go installs tools like go-acc to $HOME/go/bin
+# ubuntu minion path lacks go
+export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
+go version
+cd smoversion/
+
+# install the go coverage tool helper
+go get -v github.com/ory/go-acc
+
+export GO111MODULE=on
+go get github.com/stretchr/testify/mock@v1.7.0
+
+go-acc ./... --ignore mocks
+
+sed -i -e 's/oransc\.org\/usecase\/oduclosedloop/smoversion/' coverage.txt
+
+oransc.org/usecase/oduclosedloop/internal/config
+
+cp coverage.txt $curdir
+echo "--> build-ransliceassurance-ubuntu.sh ends"
--- /dev/null
+# The Jenkins job requires a tag to build the Docker image.
+# By default this file is in the docker build directory,
+# but the location can configured in the JJB template.
+---
+tag: 1.1.0
--- /dev/null
+# Copyright (C) 2022 Nordix Foundation. All rights reserved.
+# ========================================================================
+# 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=================================================
+#
+version: '3.5'
+
+networks:
+ default:
+ driver: bridge
+ name: nonrtric-docker-net
+
+services:
+ sdnr-mr-sim:
+ build:
+ context: .
+ dockerfile: Dockerfile-simulator
+ container_name: sdnr-mr-sim
+ networks:
+ default:
+ aliases:
+ - sdnr-mr-sim
+ ports:
+ - 3904:3904
+ - 3905:3905
+
+ odu-app:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: odu-app
+ networks:
+ default:
+ aliases:
+ - odu-app
+ ports:
+ - 8086:8086
+ environment:
+ - MR_HOST=http://sdnr-mr-sim
+ - MR_PORT=3905
+ - SDNR_ADDR=http://sdnr-mr-sim:3904
+ - SDNR_USER=admin
+ - SDNR_PASSWORD=Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U
+ - LOG_LEVEL=Info
+ - POLLTIME=10
\ No newline at end of file
--- /dev/null
+module oransc.org/usecase/oduclosedloop
+
+go 1.17
+
+require github.com/sirupsen/logrus v1.8.1
+
+require (
+ github.com/gorilla/mux v1.8.0
+ github.com/stretchr/testify v1.3.0
+)
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
+)
--- /dev/null
+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/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+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/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
--- /dev/null
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
--- /dev/null
+# Copyright (C) 2021 Nordix Foundation. All rights reserved.
+# ========================================================================
+# 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=================================================
+
+apiVersion: v1
+name: odu-app
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "1.16.0"
--- /dev/null
+{{/*
+# Copyright (C) 2021 Nordix Foundation. All rights reserved.
+# ========================================================================
+# 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=================================================
+*/}}
+
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "odu-app.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "odu-app.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "odu-app.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "odu-app.labels" -}}
+helm.sh/chart: {{ include "odu-app.chart" . }}
+{{ include "odu-app.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "odu-app.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "odu-app.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "odu-app.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "odu-app.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
--- /dev/null
+# Copyright (C) 2021 Nordix Foundation. All rights reserved.
+# ========================================================================
+# 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=================================================
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ include "odu-app.fullname" . }}
+ labels:
+ {{- include "odu-app.labels" . | nindent 4 }}
+spec:
+ replicas: {{ .Values.replicaCount }}
+ selector:
+ matchLabels:
+ {{- include "odu-app.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ {{- with .Values.podAnnotations }}
+ annotations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ labels:
+ {{- include "odu-app.selectorLabels" . | nindent 8 }}
+ spec:
+ {{- with .Values.imagePullSecrets }}
+ imagePullSecrets:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ securityContext:
+ {{- toYaml .Values.podSecurityContext | nindent 8 }}
+ containers:
+ - name: {{ .Chart.Name }}
+ securityContext:
+ {{- toYaml .Values.securityContext | nindent 12 }}
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ env:
+ - name: MR_HOST
+ value: "{{ .Values.messagerouter.host }}"
+ - name: MR_PORT
+ value: "{{ .Values.messagerouter.port }}"
+ - name: SDNR_ADDRESS
+ value: "{{ .Values.sdnr.address }}"
+ ports:
+ - name: http
+ containerPort: 80
+ protocol: TCP
+ resources:
+ {{- toYaml .Values.resources | nindent 12 }}
+ {{- with .Values.nodeSelector }}
+ nodeSelector:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.affinity }}
+ affinity:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.tolerations }}
+ tolerations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
--- /dev/null
+# Copyright (C) 2021 Nordix Foundation. All rights reserved.
+# ========================================================================
+# 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=================================================
+
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ include "odu-app.fullname" . }}
+ labels:
+ {{- include "odu-app.labels" . | nindent 4 }}
+spec:
+ type: {{ .Values.service.type }}
+ ports:
+ - port: {{ .Values.service.port }}
+ targetPort: http
+ protocol: TCP
+ name: http
+ selector:
+ {{- include "odu-app.selectorLabels" . | nindent 4 }}
--- /dev/null
+# Copyright (C) 2021 Nordix Foundation. All rights reserved.
+# ========================================================================
+# 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=================================================
+
+# Default values for odu-app.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+replicaCount: 1
+
+image:
+ repository: odu-app
+ pullPolicy: IfNotPresent
+ # Overrides the image tag whose default is the chart appVersion.
+ tag: "latest"
+
+messagerouter:
+ host: http://dmaap-mr
+ port: 3904
+
+sdnr:
+ address: http://sdnr-simulator:9990
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+
+podAnnotations: {}
+
+podSecurityContext: {}
+ # fsGroup: 2000
+
+securityContext: {}
+ # capabilities:
+ # drop:
+ # - ALL
+ # readOnlyRootFilesystem: true
+ # runAsNonRoot: true
+ # runAsUser: 1000
+
+service:
+ type: ClusterIP
+ port: 80
+
+resources: {}
+ # We usually recommend not to specify default resources and to leave this as a conscious
+ # choice for the user. This also increases chances charts run on environments with little
+ # resources, such as Minikube. If you do want to specify resources, uncomment the following
+ # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+ # limits:
+ # cpu: 100m
+ # memory: 128Mi
+ # requests:
+ # cpu: 100m
+ # memory: 128Mi
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2021: 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.
+// ========================LICENSE_END===================================
+//
+
+package config
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+
+ log "github.com/sirupsen/logrus"
+)
+
+type Config struct {
+ MRHost string
+ MRPort string
+ SDNRAddress string
+ SDNRUser string
+ SDNPassword string
+ Polltime int
+ LogLevel log.Level
+}
+
+func New() *Config {
+ return &Config{
+ MRHost: getEnv("MR_HOST", ""),
+ MRPort: getEnv("MR_PORT", ""),
+ SDNRAddress: getEnv("SDNR_ADDR", "http://localhost:3904"),
+ SDNRUser: getEnv("SDNR_USER", "admin"),
+ SDNPassword: getEnv("SDNR_PASSWORD", "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U"),
+ Polltime: getEnvAsInt("Polltime", 30),
+ LogLevel: getLogLevel(),
+ }
+}
+
+func (c Config) String() string {
+ return fmt.Sprintf("[MRHost: %v, MRPort: %v, SDNRAddress: %v, SDNRUser: %v, SDNRPassword: %v, PollTime: %v, LogLevel: %v]", c.MRHost, c.MRPort, c.SDNRAddress, c.SDNRUser, c.SDNPassword, c.Polltime, c.LogLevel)
+}
+
+func getEnv(key string, defaultVal string) string {
+ if value, exists := os.LookupEnv(key); exists {
+ return value
+ }
+
+ return defaultVal
+}
+
+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
+}
+
+func getLogLevel() log.Level {
+ logLevelStr := getEnv("LOG_LEVEL", "Info")
+ 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.InfoLevel
+ }
+
+}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2021: 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.
+// ========================LICENSE_END===================================
+//
+
+package config
+
+import (
+ "bytes"
+ "os"
+ "testing"
+
+ log "github.com/sirupsen/logrus"
+ "github.com/stretchr/testify/require"
+)
+
+func TestNewEnvVarsSetConfigContainSetValues(t *testing.T) {
+ assertions := require.New(t)
+ os.Setenv("MR_HOST", "consumerHost")
+ os.Setenv("MR_PORT", "8095")
+ os.Setenv("SDNR_ADDR", "http://localhost:3904")
+ os.Setenv("SDNR_USER", "admin")
+ os.Setenv("SDNR_PASSWORD", "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U")
+ os.Setenv("Polltime", "30")
+ os.Setenv("LOG_LEVEL", "Debug")
+ t.Cleanup(func() {
+ os.Clearenv()
+ })
+ wantConfig := Config{
+ MRHost: "consumerHost",
+ MRPort: "8095",
+ SDNRAddress: "http://localhost:3904",
+ SDNRUser: "admin",
+ SDNPassword: "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U",
+ Polltime: 30,
+ LogLevel: log.DebugLevel,
+ }
+
+ got := New()
+ assertions.Equal(&wantConfig, got)
+}
+
+func TestNewFaultyIntValueSetConfigContainDefaultValueAndWarnInLog(t *testing.T) {
+ assertions := require.New(t)
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+
+ os.Setenv("Polltime", "wrong")
+ t.Cleanup(func() {
+ log.SetOutput(os.Stderr)
+ os.Clearenv()
+ })
+ wantConfig := Config{
+ MRHost: "",
+ MRPort: "",
+ SDNRAddress: "http://localhost:3904",
+ SDNRUser: "admin",
+ SDNPassword: "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U",
+ Polltime: 30,
+ LogLevel: log.InfoLevel,
+ }
+
+ got := New()
+ assertions.Equal(&wantConfig, got)
+
+ logString := buf.String()
+ assertions.Contains(logString, "Invalid int value: wrong for variable: Polltime. Default value: 30 will be used")
+}
+
+func TestNewEnvFaultyLogLevelConfigContainDefaultValues(t *testing.T) {
+ assertions := require.New(t)
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+
+ os.Setenv("LOG_LEVEL", "wrong")
+ t.Cleanup(func() {
+ log.SetOutput(os.Stderr)
+ os.Clearenv()
+ })
+ wantConfig := Config{
+ MRHost: "",
+ MRPort: "",
+ SDNRAddress: "http://localhost:3904",
+ SDNRUser: "admin",
+ SDNPassword: "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U",
+ Polltime: 30,
+ LogLevel: log.InfoLevel,
+ }
+ got := New()
+ assertions.Equal(&wantConfig, got)
+ logString := buf.String()
+ assertions.Contains(logString, "Invalid log level: wrong. Log level will be Info!")
+}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2021: 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.
+// ========================LICENSE_END===================================
+//
+
+package restclient
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/http/httputil"
+
+ log "github.com/sirupsen/logrus"
+)
+
+type RequestError struct {
+ StatusCode int
+ Body []byte
+}
+
+func (e RequestError) Error() string {
+ return fmt.Sprintf("error response with status: %v and body: %v", e.StatusCode, string(e.Body))
+}
+
+type Client struct {
+ httpClient *http.Client
+ verbose bool
+}
+
+func New(httpClient *http.Client, verbose bool) *Client {
+ return &Client{
+ httpClient: httpClient,
+ verbose: verbose,
+ }
+}
+
+func (c *Client) Get(path string, v interface{}, userInfo ...string) error {
+ var req *http.Request
+ var err error
+
+ if len(userInfo) > 1 {
+ req, err = c.newRequest(http.MethodGet, path, nil, userInfo[0], userInfo[1])
+ } else {
+ req, err = c.newRequest(http.MethodGet, path, nil)
+ }
+
+ if err != nil {
+ return fmt.Errorf("failed to create GET request: %w", err)
+ }
+
+ if err := c.doRequest(req, v); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (c *Client) Post(path string, payload interface{}, v interface{}, userInfo ...string) error {
+ var req *http.Request
+ var err error
+
+ if len(userInfo) > 1 {
+ req, err = c.newRequest(http.MethodPost, path, payload, userInfo[0], userInfo[1])
+ } else {
+ req, err = c.newRequest(http.MethodPost, path, payload)
+ }
+
+ if err != nil {
+ return fmt.Errorf("failed to create POST request: %w", err)
+ }
+
+ if err := c.doRequest(req, v); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (c *Client) Put(path string, payload interface{}, v interface{}, userName string, password string) error {
+ req, err := c.newRequest(http.MethodPut, path, payload, userName, password)
+ if err != nil {
+ return fmt.Errorf("failed to create PUT request: %w", err)
+ }
+
+ if err := c.doRequest(req, v); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (c *Client) newRequest(method, path string, payload interface{}, userInfo ...string) (*http.Request, error) {
+ var reqBody io.Reader
+
+ if payload != nil {
+ bodyBytes, err := json.Marshal(payload)
+ if err != nil {
+ return nil, fmt.Errorf("failed to marshal request body: %w", err)
+ }
+ reqBody = bytes.NewReader(bodyBytes)
+ }
+
+ req, err := http.NewRequest(method, path, reqBody)
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to create HTTP request: %w", err)
+ }
+
+ if len(userInfo) > 0 {
+ req.SetBasicAuth(userInfo[0], userInfo[1])
+ }
+
+ if reqBody != nil {
+ req.Header.Set("Content-Type", "application/json")
+ }
+
+ if c.verbose {
+ if reqDump, error := httputil.DumpRequest(req, true); error != nil {
+ fmt.Println(err)
+ } else {
+ fmt.Println(string(reqDump))
+ }
+ }
+
+ return req, nil
+}
+
+func (c *Client) doRequest(r *http.Request, v interface{}) error {
+ resp, err := c.do(r)
+ if err != nil {
+ return err
+ }
+
+ if resp == nil {
+ return nil
+ }
+ defer resp.Body.Close()
+
+ if v == nil {
+ return nil
+ }
+
+ dec := json.NewDecoder(resp.Body)
+ if err := dec.Decode(&v); err != nil {
+ return fmt.Errorf("could not parse response body: %w [%s:%s]", err, r.Method, r.URL.String())
+ }
+ log.Debugf("Http Client Response: %v\n", v)
+ return nil
+}
+
+func (c *Client) do(r *http.Request) (*http.Response, error) {
+ resp, err := c.httpClient.Do(r)
+ if err != nil {
+ return nil, fmt.Errorf("failed to make request [%s:%s]: %w", r.Method, r.URL.String(), err)
+ }
+
+ if c.verbose {
+ if responseDump, error := httputil.DumpResponse(resp, true); error != nil {
+ fmt.Println(err)
+ } else {
+ fmt.Println(string(responseDump))
+ }
+ }
+
+ if resp.StatusCode >= http.StatusOK && resp.StatusCode <= 299 {
+ return resp, nil
+ }
+
+ defer resp.Body.Close()
+ responseData, _ := io.ReadAll(resp.Body)
+
+ putError := RequestError{
+ StatusCode: resp.StatusCode,
+ Body: responseData,
+ }
+
+ return resp, putError
+}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2021: 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.
+// ========================LICENSE_END===================================
+//
+
+package restclient
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestNewRequest(t *testing.T) {
+ assertions := require.New(t)
+
+ bodyBytes, _ := json.Marshal("body")
+ succesfullReq, _ := http.NewRequest(http.MethodGet, "url", bytes.NewReader(bodyBytes))
+
+ type args struct {
+ method string
+ path string
+ payload interface{}
+ }
+ tests := []struct {
+ name string
+ args args
+ want *http.Request
+ wantErr error
+ }{
+ {
+ name: "succesfull newRequest",
+ args: args{
+ method: http.MethodGet,
+ path: "url",
+ payload: "body",
+ },
+ want: succesfullReq,
+ wantErr: nil,
+ },
+ {
+ name: "request failed json marshal",
+ args: args{
+ method: http.MethodGet,
+ path: "url",
+ payload: map[string]interface{}{
+ "foo": make(chan int),
+ },
+ },
+ want: nil,
+ wantErr: fmt.Errorf("failed to marshal request body: json: unsupported type: chan int"),
+ },
+ {
+ name: "request failed calling newRequest",
+ args: args{
+ method: "*?",
+ path: "url",
+ payload: "body",
+ },
+ want: nil,
+ wantErr: fmt.Errorf("failed to create HTTP request: net/http: invalid method \"*?\""),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ client := New(&http.Client{}, false)
+
+ req, err := client.newRequest(tt.args.method, tt.args.path, tt.args.payload)
+ if tt.wantErr != nil {
+ assertions.Equal(tt.want, req)
+ assertions.EqualError(tt.wantErr, err.Error())
+ } else {
+ assertions.Equal("url", req.URL.Path)
+ assertions.Equal("application/json", req.Header.Get("Content-Type"))
+ assertions.Empty(req.Header.Get("Authorization"))
+ assertions.Nil(err)
+ }
+
+ })
+ }
+}
+
+func TestGet(t *testing.T) {
+ assertions := require.New(t)
+ type args struct {
+ header string
+ respCode int
+ resp interface{}
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr string
+ }{
+ {
+ name: "successful GET request",
+ args: args{
+ header: "application/json",
+ respCode: http.StatusOK,
+ resp: "Success!",
+ },
+ wantErr: "",
+ },
+ {
+ name: "error GET request",
+ args: args{
+ header: "application/json",
+ respCode: http.StatusBadRequest,
+ resp: nil,
+ },
+ wantErr: "error response with status: 400 and body:",
+ },
+ }
+
+ for _, tt := range tests {
+
+ t.Run(tt.name, func(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertions.Equal(http.MethodGet, r.Method)
+ response, _ := json.Marshal(tt.args.resp)
+ w.Header().Set("Content-Type", tt.args.header)
+ w.WriteHeader(tt.args.respCode)
+ w.Write(response)
+ }))
+ defer srv.Close()
+
+ client := New(&http.Client{}, false)
+ var res interface{}
+ err := client.Get(srv.URL, &res)
+
+ if err != nil {
+ assertions.Contains(err.Error(), tt.wantErr)
+ }
+ assertions.Equal(tt.args.resp, res)
+ })
+ }
+}
+
+func TestPost(t *testing.T) {
+ header := "application/json"
+ respCode := http.StatusOK
+ resp := "Success!"
+
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ assert.Equal(t, http.MethodPost, r.Method)
+ assert.Contains(t, r.Header.Get("Content-Type"), "application/json")
+
+ var reqBody string
+ decoder := json.NewDecoder(r.Body)
+ decoder.Decode(&reqBody)
+ assert.Equal(t, reqBody, `json:"example"`)
+
+ response, _ := json.Marshal(resp)
+ w.Header().Set("Content-Type", header)
+ w.WriteHeader(respCode)
+ w.Write(response)
+ }))
+ defer srv.Close()
+
+ client := New(&http.Client{}, false)
+ payload := `json:"example"`
+ err := client.Post(srv.URL, payload, nil, "admin", "pass")
+
+ if err != nil {
+ assert.Equal(t, "", err.Error())
+ }
+}
+
+func TestPut(t *testing.T) {
+ header := "application/json"
+ respCode := http.StatusOK
+ resp := "Success!"
+
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ assert.Equal(t, http.MethodPut, r.Method)
+ assert.Contains(t, r.Header.Get("Content-Type"), "application/json")
+
+ var reqBody string
+ decoder := json.NewDecoder(r.Body)
+ decoder.Decode(&reqBody)
+ assert.Equal(t, reqBody, `json:"example"`)
+
+ response, _ := json.Marshal(resp)
+ w.Header().Set("Content-Type", header)
+ w.WriteHeader(respCode)
+ w.Write(response)
+ }))
+ defer srv.Close()
+
+ client := New(&http.Client{}, false)
+ payload := `json:"example"`
+ err := client.Put(srv.URL, payload, nil, "admin", "pass")
+
+ if err != nil {
+ assert.Equal(t, "", err.Error())
+ }
+}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2021: 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.
+// ========================LICENSE_END===================================
+//
+
+package sliceassurance
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "time"
+
+ "oransc.org/usecase/oduclosedloop/internal/config"
+ "oransc.org/usecase/oduclosedloop/internal/restclient"
+ "oransc.org/usecase/oduclosedloop/internal/structures"
+ "oransc.org/usecase/oduclosedloop/messages"
+
+ log "github.com/sirupsen/logrus"
+)
+
+const (
+ THRESHOLD_TPUT = 7000
+ DEFAULT_DEDICATED_RATIO = 15
+ NEW_DEDICATED_RATIO = 25
+ NODE_ID = "O-DU-1122"
+)
+
+type App struct {
+ client *restclient.Client
+ metricsPolicies *structures.SliceAssuranceMeas
+}
+
+var dmaapMRUrl string
+var sDNRUrl string
+var sDNRUsername string
+var sDNRPassword string
+
+func (a *App) Initialize(config *config.Config) {
+ dmaapMRUrl = config.MRHost + ":" + config.MRPort
+ sDNRUrl = config.SDNRAddress
+ sDNRUsername = config.SDNRUser
+ sDNRPassword = config.SDNPassword
+
+ a.client = restclient.New(&http.Client{}, false)
+ a.metricsPolicies = structures.NewSliceAssuranceMeas()
+}
+
+func (a *App) Run(topic string, pollTime int) {
+ for {
+ a.getMessagesFromDmaap(dmaapMRUrl + topic)
+
+ for key := range a.metricsPolicies.Metrics {
+ a.getRRMInformation(key.Duid)
+ }
+ a.updateDedicatedRatio()
+
+ time.Sleep(time.Second * time.Duration(pollTime))
+ }
+}
+
+func (a *App) getMessagesFromDmaap(path string) {
+ log.Infof("Polling new messages from DmaapMR %v", path)
+
+ //Added to work with onap-Dmaap
+ var messageStrings []string
+ if error := a.client.Get(path, &messageStrings); error != nil {
+ log.Warn("Send of Get messages from DmaapMR failed! ", error)
+ }
+
+ for _, msgString := range messageStrings {
+ var message messages.StdDefinedMessage
+ if err := json.Unmarshal([]byte(msgString), &message); err == nil {
+ for _, meas := range message.GetMeasurements() {
+ log.Infof("Create sliceMetric and check if metric exist and update existing one or create new one measurement: %+v\n", meas)
+ //Create sliceMetric and check if metric exist and update existing one or create new one
+ if _, err := a.metricsPolicies.AddOrUpdateMetric(meas); err != nil {
+ log.Error("Metric could not be added ", err)
+ }
+ }
+ } else {
+ log.Warn(err)
+ }
+ }
+}
+
+func (a *App) getRRMInformation(duid string) {
+ var duRRMPolicyRatio messages.ORanDuRestConf
+
+ log.Infof("Get RRM Information from SDNR url: %v", sDNRUrl)
+ if error := a.client.Get(getUrlForDistributedUnitFunctions(sDNRUrl, duid), &duRRMPolicyRatio, sDNRUsername, sDNRPassword); error == nil {
+ prettyPrint(duRRMPolicyRatio.DistributedUnitFunction)
+ } else {
+ log.Warn("Send of Get RRM Information failed! ", error)
+ }
+
+ for _, odu := range duRRMPolicyRatio.DistributedUnitFunction {
+ for _, policy := range odu.RRMPolicyRatio {
+ log.Infof("Add or Update policy: %+v from DU id: %v", policy.Id, duid)
+ a.metricsPolicies.AddNewPolicy(duid, policy)
+ }
+ }
+}
+
+func (a *App) updateDedicatedRatio() {
+ for _, metric := range a.metricsPolicies.Metrics {
+ policy, check := a.metricsPolicies.Policies[metric.RRMPolicyRatioId]
+ //TODO What happened if dedicated ratio is already higher that default and threshold is exceed?
+ if check && policy.PolicyDedicatedRatio <= DEFAULT_DEDICATED_RATIO {
+ log.Infof("Send Request to update DedicatedRatio for DU id: %v Policy id: %v", metric.DUId, policy.PolicyRatioId)
+ path := getUrlUpdatePolicyDedicatedRatio(sDNRUrl, metric.DUId, policy.PolicyRatioId)
+ updatePolicyMessage := policy.GetUpdateDedicatedRatioMessage(metric.SliceDiff, metric.SliceServiceType, NEW_DEDICATED_RATIO)
+ prettyPrint(updatePolicyMessage)
+ if error := a.client.Put(path, updatePolicyMessage, nil, sDNRUsername, sDNRPassword); error == nil {
+ log.Infof("Policy Dedicated Ratio for PolicyId: %v was updated to %v", policy.PolicyRatioId, NEW_DEDICATED_RATIO)
+ } else {
+ log.Warn("Send of Put Request to update DedicatedRatio failed! ", error)
+ }
+ }
+ }
+}
+
+func getUrlForDistributedUnitFunctions(host string, duid string) string {
+ return host + "/rests/data/network-topology:network-topology/topology=topology-netconf/node=" + NODE_ID + "/yang-ext:mount/o-ran-sc-du-hello-world:network-function/distributed-unit-functions=" + duid
+}
+
+func getUrlUpdatePolicyDedicatedRatio(host string, duid string, policyid string) string {
+ return host + "/rests/data/network-topology:network-topology/topology=topology-netconf/node=" + NODE_ID + "/yang-ext:mount/o-ran-sc-du-hello-world:network-function/distributed-unit-functions=" + duid + "/radio-resource-management-policy-ratio=" + policyid
+}
+
+func prettyPrint(jsonStruct interface{}) {
+ b, err := json.MarshalIndent(jsonStruct, "", " ")
+ if err != nil {
+ fmt.Println("error:", err)
+ }
+ fmt.Print(string(b))
+}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2021: 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.
+// ========================LICENSE_END===================================
+//
+
+package structures
+
+import "oransc.org/usecase/oduclosedloop/messages"
+
+type SliceMetric struct {
+ DUId string
+ CellId string
+ SliceDiff int
+ SliceServiceType int
+ RRMPolicyRatioId string
+ PM map[string]int
+}
+
+func NewSliceMetric(duid string, cellid string, sd int, sst int) *SliceMetric {
+ sm := SliceMetric{
+ DUId: duid,
+ CellId: cellid,
+ SliceDiff: sd,
+ SliceServiceType: sst,
+ }
+ sm.PM = make(map[string]int)
+ return &sm
+}
+
+type PolicyRatio struct {
+ PolicyRatioId string
+ PolicyMaxRatio int
+ PolicyMinRatio int
+ PolicyDedicatedRatio int
+}
+
+func NewPolicyRatio(id string, max_ratio int, min_ratio int, ded_ratio int) *PolicyRatio {
+ pr := PolicyRatio{
+ PolicyRatioId: id,
+ PolicyMaxRatio: max_ratio,
+ PolicyMinRatio: min_ratio,
+ PolicyDedicatedRatio: ded_ratio,
+ }
+ return &pr
+}
+
+func (pr *PolicyRatio) GetUpdateDedicatedRatioMessage(sd int, sst int, dedicatedRatio int) interface{} {
+ message := messages.RRMPolicyRatio{
+ Id: pr.PolicyRatioId,
+ AdmState: "unlocked",
+ UserLabel: pr.PolicyRatioId,
+ RRMPolicyMaxRatio: pr.PolicyMaxRatio,
+ RRMPolicyMinRatio: pr.PolicyMinRatio,
+ RRMPolicyDedicatedRatio: dedicatedRatio,
+ ResourceType: "prb",
+ RRMPolicyMembers: []messages.RRMPolicyMember{
+ {
+ MobileCountryCode: "046",
+ MobileNetworkCode: "651",
+ SliceDifferentiator: sd,
+ SliceServiceType: sst,
+ },
+ },
+ }
+ rrmPolicies := []messages.RRMPolicyRatio{message}
+
+ return struct {
+ RRMPolicies []messages.RRMPolicyRatio `json:"radio-resource-management-policy-ratio"`
+ }{
+ RRMPolicies: rrmPolicies,
+ }
+
+}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2021: 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.
+// ========================LICENSE_END===================================
+//
+
+package structures
+
+import (
+ "fmt"
+ "regexp"
+ "strconv"
+
+ log "github.com/sirupsen/logrus"
+ "oransc.org/usecase/oduclosedloop/messages"
+)
+
+type MapKey struct {
+ Duid string
+ sd int
+ sst int
+}
+
+type SliceAssuranceMeas struct {
+ Metrics map[MapKey]*SliceMetric
+ Policies map[string]*PolicyRatio
+}
+
+func NewSliceAssuranceMeas() *SliceAssuranceMeas {
+ s := SliceAssuranceMeas{}
+ s.Metrics = make(map[MapKey]*SliceMetric)
+ s.Policies = make(map[string]*PolicyRatio)
+ return &s
+}
+
+func (sa *SliceAssuranceMeas) AddNewPolicy(duid string, rrmPolicyRatio messages.RRMPolicyRatio) {
+ for _, policyMember := range rrmPolicyRatio.RRMPolicyMembers {
+ metric := sa.GetSliceMetric(duid, policyMember.SliceDifferentiator, policyMember.SliceServiceType)
+ if metric != nil {
+ pr := NewPolicyRatio(rrmPolicyRatio.Id, rrmPolicyRatio.RRMPolicyMaxRatio, rrmPolicyRatio.RRMPolicyMinRatio, rrmPolicyRatio.RRMPolicyDedicatedRatio)
+ _, check := sa.Policies[pr.PolicyRatioId]
+ if !check {
+ log.Infof(" new policy has been added %+v", *pr)
+ }
+ sa.Policies[pr.PolicyRatioId] = pr
+ metric.RRMPolicyRatioId = rrmPolicyRatio.Id
+
+ }
+ }
+}
+
+func (sa *SliceAssuranceMeas) GetSliceMetric(duid string, sd int, sst int) *SliceMetric {
+ key := MapKey{duid, sd, sst}
+ value, check := sa.Metrics[key]
+
+ if check {
+ return value
+ }
+
+ return nil
+}
+
+func (sa *SliceAssuranceMeas) AddOrUpdateMetric(meas messages.Measurement) (string, error) {
+
+ var duid string
+ var sd, sst int
+
+ regex := *regexp.MustCompile(`\/(.*)network-function\/distributed-unit-functions\[id=\'(.*)\'\]\/cell\[id=\'(.*)\'\]\/supported-measurements\[performance-measurement-type=\'(.*)\'\]\/supported-snssai-subcounter-instances\[slice-differentiator=\'(\d+)\'\]\[slice-service-type=\'(\d+)\'\]`)
+ res := regex.FindAllStringSubmatch(meas.MeasurementTypeInstanceReference, -1)
+
+ if res != nil && len(res[0]) == 7 {
+ duid = res[0][2]
+ sd = toInt(res[0][5])
+ sst = toInt(res[0][6])
+
+ key := MapKey{duid, sd, sst}
+ value, check := sa.Metrics[key]
+
+ if check {
+ sa.updateMetric(key, value, res[0][4], meas.Value)
+ } else {
+ // Only add new one if value exceeds threshold
+ sa.addMetric(res, meas.Value)
+ }
+ } else {
+ return duid, fmt.Errorf(" wrong format for MeasurementTypeInstanceReference")
+ }
+ return duid, nil
+}
+
+func (sa *SliceAssuranceMeas) addMetric(res [][]string, metricValue int) {
+ if metricValue > 700 {
+ metric := NewSliceMetric(res[0][2], res[0][3], toInt(res[0][5]), toInt(res[0][6]))
+ metric.PM[res[0][3]] = metricValue
+ key := MapKey{res[0][2], toInt(res[0][5]), toInt(res[0][6])}
+ sa.Metrics[key] = metric
+ log.Infof(" new metric has been added %+v", *metric)
+ }
+}
+
+func (sa *SliceAssuranceMeas) updateMetric(key MapKey, value *SliceMetric, metricName string, metricValue int) {
+ if metricValue < 700 {
+ delete(sa.Metrics, key)
+ log.Infof(" metric with key %+v has been deleted", key)
+ } else {
+ value.PM[metricName] = metricValue
+ log.Infof(" metric value has been updated, new value: %v", metricValue)
+ }
+}
+
+func toInt(num string) int {
+ res, err := strconv.Atoi(num)
+ if err != nil {
+ return -1
+ }
+ return res
+}
+
+func (sa *SliceAssuranceMeas) PrintStructures() {
+ fmt.Printf("SliceAssurance Metrics: \n")
+ for key, metric := range sa.Metrics {
+ fmt.Printf("Key: %+v\n", key)
+ fmt.Printf("Metric: %+v\n", metric)
+ }
+ fmt.Printf("SliceAssurance Policies: \n")
+ for key, metric := range sa.Policies {
+ fmt.Printf("Key: %+v\n", key)
+ fmt.Printf("Metric: %+v\n", metric)
+ }
+}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2021: 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.
+// ========================LICENSE_END===================================
+//
+
+package structures
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "oransc.org/usecase/oduclosedloop/messages"
+)
+
+func TestAddMetric(t *testing.T) {
+ assertions := require.New(t)
+ type args struct {
+ meas messages.Measurement
+ }
+ tests := []struct {
+ name string
+ args args
+ }{
+ {
+ name: "Test adding new metric",
+ args: args{
+ meas: messages.Measurement{
+ MeasurementTypeInstanceReference: "/o-ran-sc-du-hello-world:network-function/distributed-unit-functions[id='O-DU-1211']/cell[id='cell-1']/supported-measurements[performance-measurement-type='user-equipment-average-throughput-uplink']/supported-snssai-subcounter-instances[slice-differentiator='1'][slice-service-type='1']",
+ Value: 51232,
+ Unit: "kbit/s",
+ },
+ },
+ },
+ {
+ name: "Test with invalid input",
+ args: args{
+ meas: messages.Measurement{
+ MeasurementTypeInstanceReference: "/distributed-unit-functions[id='O-DU-1211']/cell[id='cell-1']/supported-measurements[performance-measurement-type='user-equipment-average-throughput-uplink']/supported-snssai-subcounter-instances[slice-differentiator='1'][slice-service-type='1']",
+ Value: 51232,
+ Unit: "kbit/s",
+ },
+ },
+ },
+ }
+
+ sliceAssuranceMeas := NewSliceAssuranceMeas()
+ assertions.Equal(0, len(sliceAssuranceMeas.Metrics), "Metrics is not empty, got: %d, want: %d.", len(sliceAssuranceMeas.Metrics), 0)
+
+ for i, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+
+ if i == 0 {
+ sliceAssuranceMeas.AddOrUpdateMetric(tt.args.meas)
+ assertions.Equal(1, len(sliceAssuranceMeas.Metrics), "Metrics must have one new metric, got: %d, want: %d.", len(sliceAssuranceMeas.Metrics), 1)
+
+ testMapKey := MapKey{"O-DU-1211", 1, 1}
+ assertions.Contains(sliceAssuranceMeas.Metrics, testMapKey, "Metric added with wrong values , got: %v.", sliceAssuranceMeas.Metrics[testMapKey])
+ }
+ if i == 1 {
+ _, got := sliceAssuranceMeas.AddOrUpdateMetric(tt.args.meas)
+ assertions.EqualError(got, " wrong format for MeasurementTypeInstanceReference")
+ }
+ })
+ }
+}
+
+func TestUpdateExistingMetric(t *testing.T) {
+ assertions := require.New(t)
+ meas := messages.Measurement{
+ MeasurementTypeInstanceReference: "/o-ran-sc-du-hello-world:network-function/distributed-unit-functions[id='O-DU-1211']/cell[id='cell-1']/supported-measurements[performance-measurement-type='user-equipment-average-throughput-uplink']/supported-snssai-subcounter-instances[slice-differentiator='1'][slice-service-type='1']",
+ Value: 51232,
+ Unit: "kbit/s",
+ }
+
+ updateMeas := messages.Measurement{
+ MeasurementTypeInstanceReference: "/o-ran-sc-du-hello-world:network-function/distributed-unit-functions[id='O-DU-1211']/cell[id='cell-1']/supported-measurements[performance-measurement-type='user-equipment-average-throughput-uplink']/supported-snssai-subcounter-instances[slice-differentiator='1'][slice-service-type='1']",
+ Value: 897,
+ Unit: "kbit/s",
+ }
+
+ sliceAssuranceMeas := NewSliceAssuranceMeas()
+ assertions.Equal(0, len(sliceAssuranceMeas.Metrics), "Metrics is not empty, got: %d, want: %d.", len(sliceAssuranceMeas.Metrics), 0)
+
+ sliceAssuranceMeas.AddOrUpdateMetric(meas)
+ assertions.Equal(1, len(sliceAssuranceMeas.Metrics), "Metrics must have one new metric, got: %d, want: %d.", len(sliceAssuranceMeas.Metrics), 1)
+
+ sliceAssuranceMeas.AddOrUpdateMetric(updateMeas)
+ assertions.Equal(1, len(sliceAssuranceMeas.Metrics), "Metrics must have one updated metric, got: %d, want: %d.", len(sliceAssuranceMeas.Metrics), 1)
+
+ testMapKey := MapKey{"O-DU-1211", 1, 1}
+ metricName := "user-equipment-average-throughput-uplink"
+ newMetricValue := 897
+ if sliceAssuranceMeas.Metrics[testMapKey].PM[metricName] != newMetricValue {
+ t.Errorf("Metric value was not update properly, got: %d, want: %d.", sliceAssuranceMeas.Metrics[testMapKey].PM[metricName], newMetricValue)
+ }
+
+}
+
+func TestDeleteMetricWhenValueLessThanThreshold(t *testing.T) {
+
+ meas := messages.Measurement{
+ MeasurementTypeInstanceReference: "/o-ran-sc-du-hello-world:network-function/distributed-unit-functions[id='O-DU-1211']/cell[id='cell-1']/supported-measurements[performance-measurement-type='user-equipment-average-throughput-uplink']/supported-snssai-subcounter-instances[slice-differentiator='1'][slice-service-type='1']",
+ Value: 51232,
+ Unit: "kbit/s",
+ }
+
+ newMeas := messages.Measurement{
+ MeasurementTypeInstanceReference: "/o-ran-sc-du-hello-world:network-function/distributed-unit-functions[id='O-DU-1211']/cell[id='cell-1']/supported-measurements[performance-measurement-type='user-equipment-average-throughput-uplink']/supported-snssai-subcounter-instances[slice-differentiator='1'][slice-service-type='1']",
+ Value: 50,
+ Unit: "kbit/s",
+ }
+
+ sliceAssuranceMeas := NewSliceAssuranceMeas()
+ assert.Equal(t, 0, len(sliceAssuranceMeas.Metrics), "Metrics is not empty, got: %d, want: %d.", len(sliceAssuranceMeas.Metrics), 0)
+
+ sliceAssuranceMeas.AddOrUpdateMetric(meas)
+ assert.Equal(t, 1, len(sliceAssuranceMeas.Metrics), "Metrics must have one new metric, got: %d, want: %d.", len(sliceAssuranceMeas.Metrics), 1)
+
+ sliceAssuranceMeas.AddOrUpdateMetric(newMeas)
+ assert.Equal(t, 0, len(sliceAssuranceMeas.Metrics), "Metrics must have been deleted, got: %d, want: %d.", len(sliceAssuranceMeas.Metrics), 0)
+
+}
+
+func TestAddPolicy(t *testing.T) {
+
+ meas := messages.Measurement{
+ MeasurementTypeInstanceReference: "/o-ran-sc-du-hello-world:network-function/distributed-unit-functions[id='O-DU-1211']/cell[id='cell-1']/supported-measurements[performance-measurement-type='user-equipment-average-throughput-uplink']/supported-snssai-subcounter-instances[slice-differentiator='1'][slice-service-type='1']",
+ Value: 51232,
+ Unit: "kbit/s",
+ }
+ sliceAssuranceMeas := NewSliceAssuranceMeas()
+ sliceAssuranceMeas.AddOrUpdateMetric(meas)
+
+ duid := "O-DU-1211"
+ rrmPolicyRatio := messages.RRMPolicyRatio{
+ Id: "id",
+ AdmState: "locked",
+ UserLabel: "user_label",
+ RRMPolicyMaxRatio: 0,
+ RRMPolicyMinRatio: 0,
+ RRMPolicyDedicatedRatio: 0,
+ ResourceType: "prb",
+ RRMPolicyMembers: []messages.RRMPolicyMember{{
+ MobileCountryCode: "046",
+ MobileNetworkCode: "651",
+ SliceDifferentiator: 1,
+ SliceServiceType: 1,
+ }},
+ }
+ assert.Equal(t, 0, len(sliceAssuranceMeas.Policies), "Policies is not empty, got: %d, want: %d.", len(sliceAssuranceMeas.Policies), 0)
+
+ sliceAssuranceMeas.AddNewPolicy(duid, rrmPolicyRatio)
+ assert.Equal(t, 1, len(sliceAssuranceMeas.Policies), "Policies must have one new policy, got: %d, want: %d.", len(sliceAssuranceMeas.Policies), 1)
+
+ sliceAssuranceMeas.PrintStructures()
+}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2021: 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.
+// ========================LICENSE_END===================================
+//
+
+package main
+
+import (
+ "fmt"
+ "net/http"
+
+ log "github.com/sirupsen/logrus"
+ "oransc.org/usecase/oduclosedloop/internal/config"
+ "oransc.org/usecase/oduclosedloop/internal/sliceassurance"
+)
+
+const TOPIC string = "/events/unauthenticated.VES_O_RAN_SC_HELLO_WORLD_PM_STREAMING_OUTPUT/myG/C1"
+
+var configuration *config.Config
+
+func main() {
+ configuration = config.New()
+
+ log.SetLevel(configuration.LogLevel)
+ log.SetFormatter(&log.JSONFormatter{})
+
+ log.Debug("Using configuration: ", configuration)
+
+ if err := validateConfiguration(configuration); err != nil {
+ log.Fatalf("Unable to start consumer due to configuration error: %v", err)
+ }
+
+ a := sliceassurance.App{}
+ a.Initialize(configuration)
+ go a.Run(TOPIC, configuration.Polltime)
+
+ http.HandleFunc("/status", statusHandler)
+
+ log.Fatal(http.ListenAndServe(":40936", nil))
+}
+
+func validateConfiguration(configuration *config.Config) error {
+ if configuration.MRHost == "" || configuration.MRPort == "" {
+ return fmt.Errorf("message router host and port must be provided")
+ }
+ return nil
+}
+
+func statusHandler(w http.ResponseWriter, r *http.Request) {
+ // Just respond OK to show the service is alive for now. Might be extended later.
+}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2021: 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.
+// ========================LICENSE_END===================================
+//
+
+package messages
+
+type ORanDuRestConf struct {
+ DistributedUnitFunction []DistributedUnitFunction `json:"o-ran-sc-du-hello-world:distributed-unit-functions"`
+}
+
+type DistributedUnitFunction struct {
+ Id string `json:"id"`
+ OperationalState string `json:"operational-state"`
+ AdmState string `json:"administrative-state"`
+ UserLabel string `json:"user-label"`
+ RRMPolicyRatio []RRMPolicyRatio `json:"radio-resource-management-policy-ratio"`
+ Cell []Cell `json:"cell"`
+}
+
+type RRMPolicyRatio struct {
+ Id string `json:"id"`
+ AdmState string `json:"administrative-state"`
+ UserLabel string `json:"user-label"`
+ RRMPolicyMaxRatio int `json:"radio-resource-management-policy-max-ratio"`
+ RRMPolicyMinRatio int `json:"radio-resource-management-policy-min-ratio"`
+ RRMPolicyDedicatedRatio int `json:"radio-resource-management-policy-dedicated-ratio"`
+ ResourceType string `json:"resource-type"`
+ RRMPolicyMembers []RRMPolicyMember `json:"radio-resource-management-policy-members"`
+}
+
+type RRMPolicyMember struct {
+ MobileCountryCode string `json:"mobile-country-code"`
+ MobileNetworkCode string `json:"mobile-network-code"`
+ SliceDifferentiator int `json:"slice-differentiator"`
+ SliceServiceType int `json:"slice-service-type"`
+}
+
+type Cell struct {
+ Id string `json:"id"`
+ LocalId int `json:"local-id"`
+ PhysicalCellId int `json:"physical-cell-id"`
+ BaseStationChannelBandwidth BaseStationChannelBandwidth `json:"base-station-channel-bandwidth"`
+ OperationalState string `json:"operational-state"`
+ TrackingAreaCode int `json:"tracking-area-code"`
+ AdmState string `json:"administrative-state"`
+ PublicLandMobileNetworks []PublicLandMobileNetworks `json:"public-land-mobile-networks"`
+ SupportedMeasurements []SupportedMeasurements `json:"supported-measurements"`
+ TrafficState string `json:"traffic-state"`
+ AbsoluteRadioFrequencyChannelNumber AbsoluteRadioFrequencyChannelNumber `json:"absolute-radio-frequency-channel-number"`
+ UserLabel string `json:"user-label"`
+ SynchronizationSignalBlock SynchronizationSignalBlock `json:"synchronization-signal-block"`
+}
+
+type BaseStationChannelBandwidth struct {
+ Uplink int `json:"uplink"`
+ Downlink int `json:"downlink"`
+ SupplementaryUplink int `json:"supplementary-uplink"`
+}
+
+type PublicLandMobileNetworks struct {
+ SliceDifferentiator int `json:"slice-differentiator"`
+ SliceServiceType int `json:"slice-service-type"`
+ MobileCountryCode string `json:"mobile-country-code"`
+ MobileNetworkCode string `json:"mobile-network-code"`
+}
+
+type SupportedMeasurements struct {
+ PerformanceMeasurementType string `json:"performance-measurement-type"`
+ SupportedSnssaiSubcounterInstances []SupportedSnssaiSubcounterInstances `json:"supported-snssai-subcounter-instances"`
+}
+
+type SupportedSnssaiSubcounterInstances struct {
+ SliceDifferentiator int `json:"slice-differentiator"`
+ SliceServiceType int `json:"slice-service-type"`
+}
+
+type AbsoluteRadioFrequencyChannelNumber struct {
+ Uplink int `json:"uplink"`
+ Downlink int `json:"downlink"`
+ SupplementaryUplink int `json:"supplementary-uplink"`
+}
+
+type SynchronizationSignalBlock struct {
+ Duration int `json:"duration"`
+ FrequencyChannelNumber int `json:"frequency-channel-number"`
+ Periodicity int `json:"periodicity"`
+ SubcarrierSpacing int `json:"subcarrier-spacing"`
+ Offset int `json:"offset"`
+}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2021: 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.
+// ========================LICENSE_END===================================
+//
+
+package messages
+
+type StdDefinedMessage struct {
+ Event Event `json:"event"`
+}
+
+type Event struct {
+ CommonEventHeader CommonEventHeader `json:"commonEventHeader"`
+ StndDefinedFields StndDefinedFields `json:"stndDefinedFields"`
+}
+
+type CommonEventHeader struct {
+ Domain string `json:"domain"`
+ EventId string `json:"eventId"`
+ EventName string `json:"eventName"`
+ EventType string `json:"eventType"`
+ Sequence int `json:"sequence"`
+ Priority string `json:"priority"`
+ ReportingEntityId string `json:"reportingEntityId"`
+ ReportingEntityName string `json:"reportingEntityName"`
+ SourceId string `json:"sourceId"`
+ SourceName string `json:"sourceName"`
+ StartEpochMicrosec int64 `json:"startEpochMicrosec"`
+ LastEpochMicrosec int64 `json:"lastEpochMicrosec"`
+ NfNamingCode string `json:"nfNamingCode"`
+ NfVendorName string `json:"nfVendorName"`
+ StndDefinedNamespace string `json:"stndDefinedNamespace"`
+ TimeZoneOffset string `json:"timeZoneOffset"`
+ Version string `json:"version"`
+ VesEventListenerVersion string `json:"vesEventListenerVersion"`
+}
+
+type StndDefinedFields struct {
+ StndDefinedFieldsVersion string `json:"stndDefinedFieldsVersion"`
+ SchemaReference string `json:"schemaReference"`
+ Data Data `json:"data"`
+}
+
+type Data struct {
+ DataId string `json:"id"`
+ StartTime string `json:"start-time"`
+ AdministrativeState string `json:"administrative-state"`
+ OperationalState string `json:"operational-state"`
+ UserLabel string `json:"user-label"`
+ JobTag string `json:"job-tag"`
+ GranularityPeriod int `json:"granularity-period"`
+ Measurements []Measurement `json:"measurements"`
+}
+
+type Measurement struct {
+ MeasurementTypeInstanceReference string `json:"measurement-type-instance-reference"`
+ Value int `json:"value"`
+ Unit string `json:"unit"`
+}
+
+func (message StdDefinedMessage) GetMeasurements() []Measurement {
+ return message.Event.StndDefinedFields.Data.Measurements
+}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2021: 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.
+// ========================LICENSE_END===================================
+//
+
+package messages
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestGetMeasurements(t *testing.T) {
+ assertions := require.New(t)
+ type fields struct {
+ Event Event
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want []Measurement
+ }{
+ {
+ name: "get measurements message",
+ fields: fields{
+ Event: Event{
+ CommonEventHeader: CommonEventHeader{
+ Domain: "stndDefined",
+ StndDefinedNamespace: "o-ran-sc-du-hello-world-pm-streaming-oas3",
+ },
+ StndDefinedFields: StndDefinedFields{
+ StndDefinedFieldsVersion: "1.0",
+ SchemaReference: "https://gerrit.o-ran-sc.org/r/gitweb?p=scp/oam/modeling.git;a=blob_plain;f=data-model/oas3/experimental/o-ran-sc-du-hello-world-oas3.json;hb=refs/heads/master",
+ Data: Data{
+ DataId: "id",
+ Measurements: []Measurement{{
+ MeasurementTypeInstanceReference: "/o-ran-sc-du-hello-world:network-function/distributed-unit-functions[id='O-DU-1211']/cell[id='cell-1']/supported-measurements[performance-measurement-type='user-equipment-average-throughput-uplink']/supported-snssai-subcounter-instances[slice-differentiator='1'][slice-service-type='1']",
+ Value: 51232,
+ Unit: "kbit/s",
+ }},
+ },
+ },
+ },
+ },
+ want: []Measurement{{
+ MeasurementTypeInstanceReference: "/o-ran-sc-du-hello-world:network-function/distributed-unit-functions[id='O-DU-1211']/cell[id='cell-1']/supported-measurements[performance-measurement-type='user-equipment-average-throughput-uplink']/supported-snssai-subcounter-instances[slice-differentiator='1'][slice-service-type='1']",
+ Value: 51232,
+ Unit: "kbit/s",
+ }},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ message := StdDefinedMessage{
+ Event: tt.fields.Event,
+ }
+ var got []Measurement
+ if got = message.GetMeasurements(); len(got) != len(tt.want) {
+ t.Errorf("Message.GetMeasurements() = %v, want %v", got, tt.want)
+ }
+
+ for _, meas := range got {
+ assertions.Equal(51232, meas.Value)
+ assertions.Contains(meas.MeasurementTypeInstanceReference, "user-equipment-average-throughput-uplink")
+ }
+
+ })
+ }
+}
--- /dev/null
+FROM golang:1.15.2-alpine3.12 as build
+RUN apk add git
+RUN mkdir /build
+ADD . /build
+WORKDIR /build
+RUN go build -o simulator .
+
+FROM alpine:latest
+RUN mkdir /app
+WORKDIR /app/
+
+# Copy the Pre-built binary file from the previous stage
+COPY --from=build /build .
+
+# Expose port 8080
+EXPOSE 8080
+
+# Run Executable
+ENTRYPOINT ["/stub"]
\ No newline at end of file
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2021: 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.
+// ========================LICENSE_END===================================
+//
+
+package main
+
+import (
+ "encoding/csv"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "math/rand"
+ "net/http"
+ "os"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/gorilla/mux"
+ "oransc.org/usecase/oduclosedloop/messages"
+
+ log "github.com/sirupsen/logrus"
+)
+
+const THRESHOLD_TPUT int = 7000
+
+type SliceAssuranceInformation struct {
+ duId string
+ cellId string
+ sd int
+ sst int
+ metricName string
+ metricValue int
+ policyRatioId string
+ policyMaxRatio int
+ policyMinRatio int
+ policyDedicatedRatio int
+}
+
+var data []*SliceAssuranceInformation
+var messagesToSend []messages.Measurement
+
+func loadData() {
+ lines, err := GetCsvFromFile("test-data.csv")
+ if err != nil {
+ panic(err)
+ }
+ for _, line := range lines {
+ sai := SliceAssuranceInformation{
+ duId: line[0],
+ cellId: line[1],
+ sd: toInt(line[2]),
+ sst: toInt(line[3]),
+ metricName: line[4],
+ metricValue: toInt(line[5]),
+ policyRatioId: line[6],
+ policyMaxRatio: toInt(line[7]),
+ policyMinRatio: toInt(line[8]),
+ policyDedicatedRatio: toInt(line[9]),
+ }
+ data = append(data, &sai)
+ }
+}
+
+func GetCsvFromFile(name string) ([][]string, error) {
+ if csvFile, err := os.Open(name); err == nil {
+ defer csvFile.Close()
+ reader := csv.NewReader(csvFile)
+ reader.FieldsPerRecord = -1
+ if csvData, err := reader.ReadAll(); err == nil {
+ return csvData, nil
+ } else {
+ return nil, err
+ }
+ } else {
+ return nil, err
+ }
+}
+
+func toInt(num string) int {
+ res, err := strconv.Atoi(num)
+ if err != nil {
+ return -1
+ }
+ return res
+}
+
+func main() {
+ rand.Seed(time.Now().UnixNano())
+
+ portSdnr := flag.Int("sdnr-port", 3904, "The port this SDNR stub will listen on")
+ portDmaapMR := flag.Int("dmaap-port", 3905, "The port this Dmaap message router will listen on")
+ flag.Parse()
+
+ loadData()
+
+ wg := new(sync.WaitGroup)
+ wg.Add(2)
+
+ go func() {
+
+ r := mux.NewRouter()
+ r.HandleFunc("/rests/data/network-topology:network-topology/topology=topology-netconf/node={NODE-ID}/yang-ext:mount/o-ran-sc-du-hello-world:network-function/distributed-unit-functions={O-DU-ID}", getSdnrResponseMessage).Methods(http.MethodGet)
+ r.HandleFunc("/rests/data/network-topology:network-topology/topology=topology-netconf/node={NODE-ID}/yang-ext:mount/o-ran-sc-du-hello-world:network-function/distributed-unit-functions={O-DU-ID}/radio-resource-management-policy-ratio={POLICY-ID}", updateRRMPolicyDedicatedRatio).Methods(http.MethodPut)
+
+ fmt.Println("Starting SDNR stub on port: ", *portSdnr)
+
+ log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", *portSdnr), r))
+ wg.Done()
+ }()
+
+ go func() {
+
+ r := mux.NewRouter()
+ r.HandleFunc("/events/unauthenticated.VES_O_RAN_SC_HELLO_WORLD_PM_STREAMING_OUTPUT/myG/C1", sendDmaapMRMessages).Methods(http.MethodGet)
+
+ fmt.Println("Starting DmaapMR stub on port: ", *portDmaapMR)
+
+ log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", *portDmaapMR), r))
+ wg.Done()
+ }()
+
+ wg.Wait()
+}
+
+func getSdnrResponseMessage(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ log.Info("Get messages for RRM Policy Ratio information for O-Du ID ", vars["O-DU-ID"])
+
+ distUnitFunctions := getDistributedUnitFunctionMessage(vars["O-DU-ID"])
+
+ respondWithJSON(w, http.StatusOK, distUnitFunctions)
+}
+
+func getDistributedUnitFunctionMessage(oduId string) messages.ORanDuRestConf {
+
+ var policies []messages.RRMPolicyRatio
+ keys := make(map[string]bool)
+ for _, entry := range data {
+ if _, value := keys[entry.policyRatioId]; !value {
+ keys[entry.policyRatioId] = true
+ message := messages.RRMPolicyRatio{
+
+ Id: entry.policyRatioId,
+ AdmState: "locked",
+ UserLabel: entry.policyRatioId,
+ RRMPolicyMaxRatio: entry.policyMaxRatio,
+ RRMPolicyMinRatio: entry.policyMinRatio,
+ RRMPolicyDedicatedRatio: entry.policyDedicatedRatio,
+ ResourceType: "prb",
+ RRMPolicyMembers: []messages.RRMPolicyMember{
+ {
+ MobileCountryCode: "310",
+ MobileNetworkCode: "150",
+ SliceDifferentiator: entry.sd,
+ SliceServiceType: entry.sst,
+ },
+ },
+ }
+ policies = append(policies, message)
+ }
+ }
+
+ var publicLandMobileNetworks []messages.PublicLandMobileNetworks
+ for _, entry := range data {
+ publicLandMobileNetwork := messages.PublicLandMobileNetworks{
+ MobileCountryCode: "310",
+ MobileNetworkCode: "150",
+ SliceDifferentiator: entry.sd,
+ SliceServiceType: entry.sst,
+ }
+ publicLandMobileNetworks = append(publicLandMobileNetworks, publicLandMobileNetwork)
+ }
+
+ var supportedSnssaiSubcounterInstances []messages.SupportedSnssaiSubcounterInstances
+ for _, entry := range data {
+ supportedSnssaiSubcounterInstance := messages.SupportedSnssaiSubcounterInstances{
+ SliceDifferentiator: entry.sd,
+ SliceServiceType: entry.sst,
+ }
+ supportedSnssaiSubcounterInstances = append(supportedSnssaiSubcounterInstances, supportedSnssaiSubcounterInstance)
+ }
+
+ cell := messages.Cell{
+ Id: "cell-1",
+ LocalId: 1,
+ PhysicalCellId: 1,
+ BaseStationChannelBandwidth: messages.BaseStationChannelBandwidth{
+ Uplink: 83000,
+ Downlink: 80000,
+ SupplementaryUplink: 84000,
+ },
+ OperationalState: "enabled",
+ TrackingAreaCode: 10,
+ AdmState: "unlocked",
+ PublicLandMobileNetworks: publicLandMobileNetworks,
+ SupportedMeasurements: []messages.SupportedMeasurements{
+ {
+ PerformanceMeasurementType: "o-ran-sc-du-hello-world:user-equipment-average-throughput-uplink",
+ SupportedSnssaiSubcounterInstances: supportedSnssaiSubcounterInstances,
+ },
+ {
+ PerformanceMeasurementType: "o-ran-sc-du-hello-world:user-equipment-average-throughput-downlink",
+ SupportedSnssaiSubcounterInstances: supportedSnssaiSubcounterInstances,
+ },
+ },
+ TrafficState: "active",
+ AbsoluteRadioFrequencyChannelNumber: messages.AbsoluteRadioFrequencyChannelNumber{
+ Uplink: 14000,
+ Downlink: 15000,
+ SupplementaryUplink: 14500,
+ },
+ UserLabel: "cell-1",
+ SynchronizationSignalBlock: messages.SynchronizationSignalBlock{
+ Duration: 2,
+ FrequencyChannelNumber: 12,
+ Periodicity: 10,
+ SubcarrierSpacing: 30,
+ Offset: 3,
+ },
+ }
+
+ distUnitFunction := messages.DistributedUnitFunction{
+ Id: oduId,
+ OperationalState: "enabled",
+ AdmState: "unlocked",
+ UserLabel: oduId,
+ Cell: []messages.Cell{
+ cell,
+ },
+ RRMPolicyRatio: policies,
+ }
+
+ duRRMPolicyRatio := messages.ORanDuRestConf{
+ DistributedUnitFunction: []messages.DistributedUnitFunction{
+ distUnitFunction,
+ },
+ }
+
+ return duRRMPolicyRatio
+}
+
+func updateRRMPolicyDedicatedRatio(w http.ResponseWriter, r *http.Request) {
+ var policies struct {
+ RRMPolicies []messages.RRMPolicyRatio `json:"radio-resource-management-policy-ratio"`
+ }
+ decoder := json.NewDecoder(r.Body)
+
+ if err := decoder.Decode(&policies); err != nil {
+ respondWithError(w, http.StatusBadRequest, "Invalid request payload")
+ return
+ }
+ defer r.Body.Close()
+
+ prMessages := policies.RRMPolicies
+ log.Infof("Post request to update RRMPolicyDedicatedRatio %+v", prMessages)
+ findAndUpdatePolicy(prMessages)
+ respondWithJSON(w, http.StatusOK, map[string]string{"status": "200"})
+}
+
+func findAndUpdatePolicy(rRMPolicyRatio []messages.RRMPolicyRatio) {
+ for _, policy := range rRMPolicyRatio {
+ for _, entry := range data {
+ if entry.policyRatioId == policy.Id {
+ log.Infof("update Policy Dedicated Ratio: value for policy %+v\n Old value: %v New value: %v ", policy, entry.policyDedicatedRatio, policy.RRMPolicyDedicatedRatio)
+ entry.policyDedicatedRatio = policy.RRMPolicyDedicatedRatio
+ if entry.metricValue > THRESHOLD_TPUT {
+ entry.metricValue = rand.Intn(THRESHOLD_TPUT)
+ }
+ messagesToSend = append(messagesToSend, generateMeasurementEntry(entry))
+ }
+ }
+ }
+}
+
+func respondWithError(w http.ResponseWriter, code int, message string) {
+ respondWithJSON(w, code, map[string]string{"error": message})
+}
+
+func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
+ response, _ := json.Marshal(payload)
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(code)
+ w.Write(response)
+}
+
+func sendDmaapMRMessages(w http.ResponseWriter, r *http.Request) {
+ log.Info("Send Dmaap messages")
+ entry := data[rand.Intn(5)]
+
+ maxTput := THRESHOLD_TPUT + 100
+ randomTput := rand.Intn(maxTput-THRESHOLD_TPUT+1) + THRESHOLD_TPUT
+ if randomTput%3 == 0 {
+ log.Info("Using tput value higher than THRESHOLD_TPUT ", randomTput)
+ entry.metricValue = randomTput
+ }
+ randomEventId := rand.Intn(10000)
+ messagesToSend = append(messagesToSend, generateMeasurementEntry(entry))
+
+ message := messages.StdDefinedMessage{
+ Event: messages.Event{
+ CommonEventHeader: messages.CommonEventHeader{
+ Domain: "stndDefined",
+ EventId: "pm-1_16442" + strconv.Itoa(randomEventId),
+ EventName: "stndDefined_performanceMeasurementStreaming",
+ EventType: "performanceMeasurementStreaming",
+ Sequence: 825,
+ Priority: "Low",
+ ReportingEntityId: "",
+ ReportingEntityName: "O-DU-1122",
+ SourceId: "",
+ SourceName: "O-DU-1122",
+ StartEpochMicrosec: 1644252450000000,
+ LastEpochMicrosec: 1644252480000000,
+ NfNamingCode: "SIM-O-DU",
+ NfVendorName: "O-RAN-SC SIM Project",
+ StndDefinedNamespace: "o-ran-sc-du-hello-world-pm-streaming-oas3",
+ TimeZoneOffset: "+00:00",
+ Version: "4.1",
+ VesEventListenerVersion: "7.2.1",
+ },
+ StndDefinedFields: messages.StndDefinedFields{
+ StndDefinedFieldsVersion: "1.0",
+ SchemaReference: "https://gerrit.o-ran-sc.org/r/gitweb?p=scp/oam/modeling.git;a=blob_plain;f=data-model/oas3/experimental/o-ran-sc-du-hello-world-oas3.json;hb=refs/heads/master",
+ Data: messages.Data{
+ DataId: "pm-1_1644252450",
+ StartTime: "2022-02-07T16:47:30.0Z",
+ AdministrativeState: "unlocked",
+ OperationalState: "enabled",
+ UserLabel: "pm",
+ JobTag: "my-job-tag",
+ GranularityPeriod: 30,
+ Measurements: messagesToSend,
+ },
+ },
+ },
+ }
+
+ fmt.Printf("Sending Dmaap message:\n %+v\n", message)
+
+ messageAsByteArray, _ := json.Marshal(message)
+ response := [1]string{string(messageAsByteArray)}
+
+ time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
+ respondWithJSON(w, http.StatusOK, response)
+
+ messagesToSend = nil
+}
+
+func generateMeasurementEntry(entry *SliceAssuranceInformation) messages.Measurement {
+
+ measurementTypeInstanceReference := "/o-ran-sc-du-hello-world:network-function/distributed-unit-functions[id='" + entry.duId + "']/cell[id='" + entry.cellId + "']/supported-measurements[performance-measurement-type='(urn:o-ran-sc:yang:o-ran-sc-du-hello-world?revision=2021-11-23)" + entry.metricName + "']/supported-snssai-subcounter-instances[slice-differentiator='" + strconv.Itoa(entry.sd) + "'][slice-service-type='" + strconv.Itoa(entry.sst) + "']"
+ meas := messages.Measurement{
+
+ MeasurementTypeInstanceReference: measurementTypeInstanceReference,
+ Value: entry.metricValue,
+ Unit: "kbit/s",
+ }
+ return meas
+}
--- /dev/null
+O-DU-1122,cell-1,1,1,user-equipment-average-throughput-downlink,3761,rrm-pol-1,20,10,15
+O-DU-1122,cell-1,1,1,user-equipment-average-throughput-uplink,5861,rrm-pol-1,20,10,15
+O-DU-1122,cell-1,1,2,user-equipment-average-throughput-downlink,7791,rrm-pol-2,20,10,15
+O-DU-1122,cell-1,1,2,user-equipment-average-throughput-uplink,4539,rrm-pol-2,20,10,15
+O-DU-1122,cell-1,2,1,user-equipment-average-throughput-downlink,8987,rrm-pol-3,20,10,15
+O-DU-1122,cell-1,2,1,user-equipment-average-throughput-uplink,1134,rrm-pol-3,20,10,15
+O-DU-1122,cell-1,2,2,user-equipment-average-throughput-downlink,9123,rrm-pol-4,20,10,15
+O-DU-1122,cell-1,2,2,user-equipment-average-throughput-uplink,5368,rrm-pol-4,20,10,15
+O-DU-1122,cell-1,3,1,user-equipment-average-throughput-downlink,8764,rrm-pol-5,20,10,15
+O-DU-1122,cell-1,3,1,user-equipment-average-throughput-uplink,1367,rrm-pol-5,20,10,15
\ No newline at end of file
--- /dev/null
+# ==================================================================================
+# Copyright (c) 2020 Nordix
+#
+# 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.
+# ==================================================================================
+
+# documentation only
+[tox]
+minversion = 2.0
+envlist =
+ docs,
+ docs-linkcheck,
+skipsdist = true
+
+[testenv:docs]
+basepython = python3
+deps = -r{toxinidir}/docs/requirements-docs.txt
+
+commands =
+ sphinx-build -W -b html -n -d {envtmpdir}/docs/doctrees ./docs/ {toxinidir}/docs/_build/html
+ echo "Generated docs available in {toxinidir}/docs/_build/html"
+whitelist_externals = echo
+
+[testenv:docs-linkcheck]
+basepython = python3
+deps = -r{toxinidir}/docs/requirements-docs.txt
+commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees ./docs/ {toxinidir}/docs/_build/linkcheck