--- /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
+# 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_ADDRESS 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
+
+
+## Development
+
+To make it easy to test during development of the consumer, two stubs are provided in the `stub` folder.
+
+One, under the `mrstub` folder, called `mrstub` that stubs the VES message received from Dmaap and pushes messages with information about performance measurements for the slices in a determinated DU. To build and start the stub, do the following:
+>1. cd stub/producer
+>2. go build
+>3. ./mrstub
+
+One, under the `sdnr` folder, called `sdnr` that at startup will listen for REST calls and print the body of them. By default, it listens to the port `3904`, but his can be overridden by passing a `-port [PORT]` flag when starting the stub. To build and start the stub, do the following:
+>1. cd stub/sdnr
+>2. go build
+>3. ./sdnr
+
+
+## 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
+module oransc.org/usecase/oduclosedloop
+
+go 1.17
+
+require github.com/sirupsen/logrus v1.8.1
+
+require github.com/gorilla/mux v1.8.0
+
+require golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
--- /dev/null
+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/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+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
+// -
+// ========================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", 10),
+ LogLevel: getLogLevel(),
+ }
+}
+
+func (c Config) String() string {
+ return fmt.Sprintf("ConsumerHost: %v, ConsumerPort: %v, SDNRAddress: %v, SDNRUser: %v, SDNRPassword: %v, LogLevel: %v", c.MRHost, c.MRPort, c.SDNRAddress, c.SDNRUser, c.SDNPassword, 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 restclient
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+)
+
+type Client struct {
+ httpClient *http.Client
+}
+
+func New(httpClient *http.Client) *Client {
+ return &Client{
+ httpClient: httpClient,
+ }
+}
+
+type HTTPClient interface {
+ Get(path string, v interface{}) error
+ Post(path string, payload interface{}, v interface{}) error
+}
+
+func (c *Client) Get(path string, v interface{}) error {
+ 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{}) error {
+ 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) newRequest(method, path string, payload interface{}) (*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 reqBody != nil {
+ req.Header.Set("Content-Type", "application/json; charset=utf-8")
+ }
+ fmt.Printf("Http Client Request: [%s:%s]\n", req.Method, req.URL)
+ 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())
+ }
+ fmt.Printf("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 resp.StatusCode >= http.StatusOK && resp.StatusCode <= 299 {
+ return resp, nil
+ }
+
+ defer resp.Body.Close()
+
+ return resp, fmt.Errorf("failed to do request, %d status code received", resp.StatusCode)
+}
--- /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 (
+ "fmt"
+ "net/http"
+ "strconv"
+ "time"
+
+ "oransc.org/usecase/oduclosedloop/internal/restclient"
+ "oransc.org/usecase/oduclosedloop/internal/structures"
+ "oransc.org/usecase/oduclosedloop/messages"
+)
+
+const (
+ THRESHOLD_TPUT = 700
+ DEFAULT_DEDICATED_RATIO = 40
+ NEW_DEDICATED_RATIO = 50
+ NODE_ID = "O-DU-1211"
+)
+
+type App struct {
+ Client restclient.HTTPClient
+ MetricsPolicies *structures.SliceAssuranceMeas
+}
+
+var dmaapMRUrl string
+var SDNRUrl string
+
+func (a *App) Initialize(dmaapUrl string, sdnrUrl string) {
+ dmaapMRUrl = dmaapUrl
+ SDNRUrl = sdnrUrl
+
+ a.Client = restclient.New(&http.Client{})
+ a.MetricsPolicies = structures.NewSliceAssuranceMeas()
+}
+
+func (a *App) Run(topic string, pollTime int) {
+ for {
+ fmt.Printf("Polling new messages from DmaapMR\n")
+ var stdMessage messages.StdDefinedMessage
+
+ a.Client.Get(dmaapMRUrl+topic, &stdMessage)
+
+ a.processMessages(stdMessage)
+
+ exceedsThMetrics := a.checkIfThresholdIsExceed()
+ if len(exceedsThMetrics) > 0 {
+ a.updateDedicatedRatio(exceedsThMetrics)
+ }
+
+ time.Sleep(time.Second * time.Duration(pollTime))
+ }
+}
+
+func (a *App) processMessages(stdMessage messages.StdDefinedMessage) {
+
+ for _, meas := range stdMessage.GetMeasurements() {
+
+ fmt.Printf("New measurement: %+v\n", meas)
+ //Create sliceMetric and check if metric exist and update existing one or create new one
+ tmpSm := meas.CreateSliceMetric()
+ a.MetricsPolicies.AddOrUpdateMetric(tmpSm)
+
+ //Fetch policy ratio metrics from SDNR
+ var duRRMPolicyRatio messages.ORanDuRestConf
+ a.Client.Get(getUrlForDistributedUnitFunctions(SDNRUrl, tmpSm.DUId), &duRRMPolicyRatio)
+
+ //Get DuId and check if we have metrics for it
+ policyRatioDuId := duRRMPolicyRatio.DistributedUnitFunction.Id
+ policies := duRRMPolicyRatio.DistributedUnitFunction.RRMPolicyRatio
+ for _, policy := range policies {
+ members:
+ for _, member := range policy.RRMPolicyMembers {
+ metric := a.MetricsPolicies.GetSliceMetric(policyRatioDuId, member.SliceDifferentiator, member.SliceServiceType)
+ if metric != nil {
+ a.MetricsPolicies.AddNewPolicy(addOrUpdatePolicyRatio(metric, policy))
+ break members
+ }
+ }
+ }
+ }
+}
+
+func (a *App) checkIfThresholdIsExceed() []*structures.SliceMetric {
+ exceedsThMetrics := make([]*structures.SliceMetric, 0)
+ for _, metric := range a.MetricsPolicies.Metrics {
+ for key, value := range metric.PM {
+
+ if (value) > THRESHOLD_TPUT {
+ fmt.Printf("PM: [%v, %v] exceeds threshold value!\n", key, value)
+ exceedsThMetrics = append(exceedsThMetrics, metric)
+ }
+ }
+ }
+ return exceedsThMetrics
+}
+
+func (a *App) updateDedicatedRatio(exceedsThMetrics []*structures.SliceMetric) {
+ for _, m := range exceedsThMetrics {
+ //Check if RRMPolicyDedicatedRatio is higher than default value
+ policy := a.MetricsPolicies.Policies[m.RRMPolicyRatioId]
+
+ if policy != nil && policy.PolicyDedicatedRatio <= DEFAULT_DEDICATED_RATIO {
+ //Send PostRequest to update DedicatedRatio
+ url := getUrlUpdatePolicyDedicatedRatio(SDNRUrl, m.DUId, policy.PolicyRatioId)
+ a.Client.Post(url, messages.GetDedicatedRatioUpdateMessage(*m, *policy, NEW_DEDICATED_RATIO), nil)
+ }
+ }
+}
+
+func addOrUpdatePolicyRatio(metric *structures.SliceMetric, policy messages.RRMPolicyRatio) *structures.PolicyRatio {
+ if metric.RRMPolicyRatioId == "" {
+ metric.RRMPolicyRatioId = policy.Id
+ }
+ return &structures.PolicyRatio{
+ PolicyRatioId: policy.Id,
+ PolicyMaxRatio: policy.RRMPolicyMaxRatio,
+ PolicyMinRatio: policy.RRMPolicyMinRatio,
+ PolicyDedicatedRatio: toInt(policy.RRMPolicyDedicatedRatio),
+ }
+}
+
+func toInt(num string) int {
+ res, err := strconv.Atoi(num)
+ if err != nil {
+ return -1
+ }
+ return res
+}
+
+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
+}
--- /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
+
+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 string
+ PolicyDedicatedRatio int
+}
+
+func NewPolicyRatioEntry(id string, max_ratio int, min_ratio string, ded_ratio int) *PolicyRatio {
+ pr := PolicyRatio{
+ PolicyRatioId: id,
+ PolicyMaxRatio: max_ratio,
+ PolicyMinRatio: min_ratio,
+ PolicyDedicatedRatio: ded_ratio,
+ }
+ return &pr
+}
--- /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
+
+type SliceAssuranceMeas struct {
+ Metrics []*SliceMetric
+ Policies map[string]*PolicyRatio
+}
+
+func NewSliceAssuranceMeas() *SliceAssuranceMeas {
+ s := SliceAssuranceMeas{}
+ s.Metrics = make([]*SliceMetric, 0)
+ s.Policies = make(map[string]*PolicyRatio)
+ return &s
+}
+
+func (sa *SliceAssuranceMeas) AddNewPolicy(pr *PolicyRatio) {
+ sa.Policies[pr.PolicyRatioId] = pr
+}
+
+func (sa *SliceAssuranceMeas) GetSliceMetric(duid string, sd int, sst int) *SliceMetric {
+ for _, metric := range sa.Metrics {
+ if metric.DUId == duid && metric.SliceDiff == sd && metric.SliceServiceType == sst {
+ return metric
+ }
+ }
+ return nil
+}
+
+func (sa *SliceAssuranceMeas) AddOrUpdateMetric(sm *SliceMetric) {
+ metric := sa.GetSliceMetric(sm.DUId, sm.SliceDiff, sm.SliceServiceType)
+ if metric != nil {
+ for key, value := range sm.PM {
+ metric.PM[key] = value
+ }
+ } else {
+ sa.Metrics = append(sa.Metrics, sm)
+ }
+}
--- /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"
+
+ log "github.com/sirupsen/logrus"
+ "oransc.org/usecase/oduclosedloop/internal/config"
+ "oransc.org/usecase/oduclosedloop/internal/sliceassurance"
+)
+
+const TOPIC string = "/events/unauthenticated.PERFORMANCE_MEASUREMENTS"
+
+var configuration *config.Config
+
+func main() {
+ configuration = config.New()
+
+ log.SetLevel(configuration.LogLevel)
+ log.Debug("Using configuration: ", configuration)
+
+ dmaapUrl := configuration.MRHost + ":" + configuration.MRPort
+
+ if err := validateConfiguration(configuration); err != nil {
+ log.Fatalf("Unable to start consumer due to configuration error: %v", err)
+ }
+
+ a := sliceassurance.App{}
+ a.Initialize(dmaapUrl, configuration.SDNRAddress)
+ a.Run(TOPIC, configuration.Polltime)
+
+}
+
+func validateConfiguration(configuration *config.Config) error {
+ if configuration.MRHost == "" || configuration.MRPort == "" {
+ return fmt.Errorf("message router host and port must be provided")
+ }
+ return nil
+}
--- /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 (
+ "strconv"
+
+ "oransc.org/usecase/oduclosedloop/internal/structures"
+)
+
+type ORanDuRestConf struct {
+ DistributedUnitFunction DistributedUnitFunction `json:"distributed-unit-functions"`
+}
+
+type DistributedUnitFunction struct {
+ Id string `json:"id"`
+ Cell []Cell `json:"cell"`
+ RRMPolicyRatio []RRMPolicyRatio `json:"radio-resource-management-policy-ratio"`
+}
+
+type Cell struct {
+ Id string `json:"id"`
+ AdmState string `json:"administrative-state"`
+ OpState string `json:"operational-state"`
+ UserLabel string `json:"user-label"`
+}
+
+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 string `json:"radio-resource-management-policy-min-ratio"`
+ RRMPolicyDedicatedRatio string `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"`
+}
+
+func GetDedicatedRatioUpdateMessage(metric structures.SliceMetric, policy structures.PolicyRatio, dedicatedRatio int) RRMPolicyRatio {
+ return RRMPolicyRatio{
+ Id: policy.PolicyRatioId,
+ AdmState: "Locked",
+ UserLabel: "Some user label",
+ RRMPolicyMaxRatio: policy.PolicyMaxRatio,
+ RRMPolicyMinRatio: policy.PolicyMinRatio,
+ RRMPolicyDedicatedRatio: strconv.Itoa(dedicatedRatio),
+ ResourceType: "prb",
+ RRMPolicyMembers: []RRMPolicyMember{
+ {
+ MobileCountryCode: "046",
+ MobileNetworkCode: "651",
+ SliceDifferentiator: metric.SliceDiff,
+ SliceServiceType: metric.SliceServiceType,
+ },
+ },
+ }
+}
--- /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 (
+ "fmt"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "oransc.org/usecase/oduclosedloop/internal/structures"
+)
+
+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"`
+ StndDefinedNamespace string `json:"stndDefinedNamespace"`
+}
+
+type StndDefinedFields struct {
+ StndDefinedFieldsVersion string `json:"stndDefinedFieldsVersion"`
+ SchemaReference string `json:"schemaReference"`
+ Data Data `json:"data"`
+}
+
+type Data struct {
+ DataId string `json:"id"`
+ 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
+}
+
+func (meas Measurement) CreateSliceMetric() *structures.SliceMetric {
+ var pmName string
+ var duid, cellid string
+ var sd, sst int
+
+ typeParts := strings.Split(meas.MeasurementTypeInstanceReference, "/")
+ for _, part := range typeParts {
+ if strings.Contains(part, "distributed-unit-functions") {
+ duid = getValueInsideQuotes(part)
+
+ } else if strings.Contains(part, "cell[") {
+ cellid = getValueInsideQuotes(part)
+
+ } else if strings.Contains(part, "performance-measurement-type") {
+ pmName = getValueInsideQuotes(part)
+
+ } else if strings.Contains(part, "slice-differentiator") {
+ sd = getPropertyNumber(part)
+
+ } else if strings.Contains(part, "slice-differentiator") {
+ res, err := strconv.Atoi(getValueInsideQuotes(part))
+ if err != nil {
+ sst = -1
+ }
+ sst = res
+ }
+ }
+
+ sm := structures.NewSliceMetric(duid, cellid, sd, sst)
+ sm.PM[pmName] = meas.Value
+ return sm
+}
+
+func getValueInsideQuotes(text string) string {
+ re := regexp.MustCompile(`\'(.*?)\'`)
+
+ match := re.FindAllString(text, -1)
+ var res string
+ if len(match) == 1 {
+ res = strings.Trim(match[0], "'")
+ }
+ return res
+}
+
+func getPropertyNumber(text string) int {
+ re := regexp.MustCompile("[0-9]+")
+ match := re.FindAllString(text, -1)
+ var res int
+ var err error
+ if len(match) == 1 {
+ res, err = strconv.Atoi(match[0])
+ if err != nil {
+ fmt.Println(err)
+ return -1
+ }
+ }
+ return res
+}
--- /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/json"
+ "flag"
+ "fmt"
+ "math/rand"
+ "net/http"
+ "time"
+
+ "github.com/gorilla/mux"
+ "oransc.org/usecase/oduclosedloop/messages"
+)
+
+func main() {
+ rand.Seed(time.Now().UnixNano())
+ port := flag.Int("port", 3905, "The port this message router will listen on")
+ flag.Parse()
+
+ r := mux.NewRouter()
+ r.HandleFunc("/events/unauthenticated.PERFORMANCE_MEASUREMENTS", sendStdMessage).Methods(http.MethodGet)
+
+ fmt.Println("Starting mr on port: ", *port)
+
+ http.ListenAndServe(fmt.Sprintf(":%v", *port), r)
+
+}
+
+// Variables ::
+// DU-ID: ERICSSON-O-DU-11220
+// Cell-ID: cell1
+// Slice-Diff: 2
+// Value: 300
+func sendStdMessage(w http.ResponseWriter, r *http.Request) {
+ message := fetchMessage()
+ fmt.Println("-----------------------------------------------------------------------------")
+ fmt.Println("Sending message: ", message)
+ fmt.Println("-----------------------------------------------------------------------------")
+ response, _ := json.Marshal(message)
+ time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ w.Write(response)
+}
+
+func fetchMessage() messages.StdDefinedMessage {
+
+ index := rand.Intn(5)
+ fmt.Println(index)
+
+ measurements := [5][]messages.Measurement{meas1, meas2, meas3, meas4, meas5}
+
+ message := messages.StdDefinedMessage{
+ Event: messages.Event{
+ CommonEventHeader: messages.CommonEventHeader{
+ Domain: "stndDefined",
+ StndDefinedNamespace: "o-ran-sc-du-hello-world-pm-streaming-oas3",
+ },
+ 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: "id",
+ Measurements: measurements[index],
+ },
+ },
+ },
+ }
+ return message
+}
+
+var meas1 = []messages.Measurement{
+ {
+ MeasurementTypeInstanceReference: "/network-function/distributed-unit-functions[id='ERICSSON-O-DU-11220']/cell[id='cell1']/supported-measurements/performance-measurement-type[.='user-equipment-average-throughput-downlink']/supported-snssai-subcounter-instances/slice-differentiator[.=2][slice-service-type=1]",
+ Value: 300,
+ Unit: "kbit/s",
+ },
+}
+
+var meas2 = []messages.Measurement{
+ {
+ MeasurementTypeInstanceReference: "/network-function/distributed-unit-functions[id='ERICSSON-O-DU-11220']/cell[id='cell1']/supported-measurements/performance-measurement-type[.='user-equipment-average-throughput-downlink']/supported-snssai-subcounter-instances/slice-differentiator[.=1]",
+ Value: 400,
+ Unit: "kbit/s",
+ },
+}
+
+var meas3 = []messages.Measurement{
+ {
+ MeasurementTypeInstanceReference: "/network-function/distributed-unit-functions[id='ERICSSON-O-DU-11220']/cell[id='cell1']/supported-measurements/performance-measurement-type[.='user-equipment-average-throughput-uplink']/supported-snssai-subcounter-instances/slice-differentiator[.=2][slice-service-type=2]",
+ Value: 800,
+ Unit: "kbit/s",
+ },
+}
+
+var meas4 = []messages.Measurement{
+ {
+ MeasurementTypeInstanceReference: "/network-function/distributed-unit-functions[id='ERICSSON-O-DU-11220']/cell[id='cell1']/supported-measurements/performance-measurement-type[.='user-equipment-average-throughput-downlink']/supported-snssai-subcounter-instances/slice-differentiator[.=1]",
+ Value: 750,
+ Unit: "kbit/s",
+ },
+}
+
+var meas5 = []messages.Measurement{
+ {
+ MeasurementTypeInstanceReference: "/network-function/distributed-unit-functions[id='ERICSSON-O-DU-11220']/cell[id='cell1']/supported-measurements/performance-measurement-type[.='user-equipment-average-throughput-downlink']/supported-snssai-subcounter-instances/[slice-differentiator[.=2]][slice-service-type=1]",
+ Value: 900,
+ Unit: "kbit/s",
+ },
+}
--- /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/json"
+ "flag"
+ "fmt"
+ "net/http"
+
+ "github.com/gorilla/mux"
+ "oransc.org/usecase/oduclosedloop/messages"
+)
+
+func main() {
+ port := flag.Int("port", 3904, "The port this SDNR stub will listen on")
+ flag.Parse()
+
+ 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}", getDistributedUnitFunctions).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.MethodPost)
+
+ fmt.Println("Starting SDNR on port: ", *port)
+ http.ListenAndServe(fmt.Sprintf(":%v", *port), r)
+
+}
+
+func getDistributedUnitFunctions(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+
+ message := messages.ORanDuRestConf{
+ DistributedUnitFunction: messages.DistributedUnitFunction{
+ Id: vars["O-DU-ID"],
+ RRMPolicyRatio: []messages.RRMPolicyRatio{
+ {
+ Id: "rrm-pol-1",
+ AdmState: "locked",
+ UserLabel: "rrm-pol-1",
+ RRMPolicyMaxRatio: 100,
+ RRMPolicyMinRatio: "0",
+ RRMPolicyDedicatedRatio: "0",
+ ResourceType: "prb",
+ RRMPolicyMembers: []messages.RRMPolicyMember{
+ {
+ MobileCountryCode: "046",
+ MobileNetworkCode: "651",
+ SliceDifferentiator: 1,
+ SliceServiceType: 0,
+ },
+ },
+ },
+ {
+ Id: "rrm-pol-2",
+ AdmState: "unlocked",
+ UserLabel: "rrm-pol-2",
+ RRMPolicyMaxRatio: 20,
+ RRMPolicyMinRatio: "10",
+ RRMPolicyDedicatedRatio: "15",
+ ResourceType: "prb",
+ RRMPolicyMembers: []messages.RRMPolicyMember{
+ {
+ MobileCountryCode: "046",
+ MobileNetworkCode: "651",
+ SliceDifferentiator: 2,
+ SliceServiceType: 1,
+ },
+ },
+ },
+ {
+ Id: "rrm-pol-3",
+ AdmState: "unlocked",
+ UserLabel: "rrm-pol-3",
+ RRMPolicyMaxRatio: 30,
+ RRMPolicyMinRatio: "10",
+ RRMPolicyDedicatedRatio: "5",
+ ResourceType: "prb",
+ RRMPolicyMembers: []messages.RRMPolicyMember{
+ {
+ MobileCountryCode: "310",
+ MobileNetworkCode: "150",
+ SliceDifferentiator: 2,
+ SliceServiceType: 2,
+ },
+ },
+ },
+ },
+ },
+ }
+
+ respondWithJSON(w, http.StatusOK, message)
+}
+
+func updateRRMPolicyDedicatedRatio(w http.ResponseWriter, r *http.Request) {
+ //vars := mux.Vars(r)
+ fmt.Println("::updateRRMPolicyDedicatedRatio::")
+ var prMessage messages.DistributedUnitFunction
+ decoder := json.NewDecoder(r.Body)
+
+ if err := decoder.Decode(&prMessage); err != nil {
+ respondWithError(w, http.StatusBadRequest, "Invalid request payload")
+ return
+ }
+ defer r.Body.Close()
+
+ fmt.Println("prMessage: ", prMessage)
+ //prMessage.Id = vars["POLICY-ID"]
+
+ respondWithJSON(w, http.StatusOK, map[string]string{"status": "200"})
+}
+
+func respondWithError(w http.ResponseWriter, code int, message string) {
+ fmt.Println("-----------------------------------------------------------------------------")
+ fmt.Println("Sending error message: ", message)
+ fmt.Println("-----------------------------------------------------------------------------")
+ respondWithJSON(w, code, map[string]string{"error": message})
+}
+
+func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
+ fmt.Println("-----------------------------------------------------------------------------")
+ fmt.Println("Sending message: ", payload)
+ fmt.Println("-----------------------------------------------------------------------------")
+ response, _ := json.Marshal(payload)
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(code)
+ w.Write(response)
+}