From: ychacon Date: Mon, 29 Nov 2021 09:17:29 +0000 (+0100) Subject: First version of ODU slice assurance usecase X-Git-Tag: 1.2.0~6^2 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=9850e546f54b85fec121518828517f1643ef81f8;hp=0f6367023720ecc7d7b4b38cbbc4282792172a89;p=nonrtric.git First version of ODU slice assurance usecase Issue-ID: NONRTRIC-637 Signed-off-by: ychacon Change-Id: I5d22039bf60c40e78eaf5a02698b6473665dd5e8 --- diff --git a/test/usecases/odusliceassurance/goversion/Dockerfile b/test/usecases/odusliceassurance/goversion/Dockerfile new file mode 100644 index 00000000..f4627684 --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/Dockerfile @@ -0,0 +1,44 @@ +#================================================================================== +# 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 diff --git a/test/usecases/odusliceassurance/goversion/README.md b/test/usecases/odusliceassurance/goversion/README.md new file mode 100644 index 00000000..7fdea74d --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/README.md @@ -0,0 +1,46 @@ +# 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 diff --git a/test/usecases/odusliceassurance/goversion/go.mod b/test/usecases/odusliceassurance/goversion/go.mod new file mode 100644 index 00000000..e2fb61d9 --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/go.mod @@ -0,0 +1,9 @@ +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 diff --git a/test/usecases/odusliceassurance/goversion/go.sum b/test/usecases/odusliceassurance/goversion/go.sum new file mode 100644 index 00000000..7ad3d91f --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/go.sum @@ -0,0 +1,13 @@ +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= diff --git a/test/usecases/odusliceassurance/goversion/internal/config/config.go b/test/usecases/odusliceassurance/goversion/internal/config/config.go new file mode 100644 index 00000000..20c0fccb --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/internal/config/config.go @@ -0,0 +1,85 @@ +// - +// ========================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 + } + +} diff --git a/test/usecases/odusliceassurance/goversion/internal/restclient/client.go b/test/usecases/odusliceassurance/goversion/internal/restclient/client.go new file mode 100644 index 00000000..81e11432 --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/internal/restclient/client.go @@ -0,0 +1,130 @@ +// - +// ========================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) +} diff --git a/test/usecases/odusliceassurance/goversion/internal/sliceassurance/app.go b/test/usecases/odusliceassurance/goversion/internal/sliceassurance/app.go new file mode 100644 index 00000000..09a94482 --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/internal/sliceassurance/app.go @@ -0,0 +1,157 @@ +// - +// ========================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 +} diff --git a/test/usecases/odusliceassurance/goversion/internal/structures/measurements.go b/test/usecases/odusliceassurance/goversion/internal/structures/measurements.go new file mode 100644 index 00000000..8c38649c --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/internal/structures/measurements.go @@ -0,0 +1,58 @@ +// - +// ========================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 +} diff --git a/test/usecases/odusliceassurance/goversion/internal/structures/sliceassurance.go b/test/usecases/odusliceassurance/goversion/internal/structures/sliceassurance.go new file mode 100644 index 00000000..b73deb21 --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/internal/structures/sliceassurance.go @@ -0,0 +1,57 @@ +// - +// ========================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) + } +} diff --git a/test/usecases/odusliceassurance/goversion/main.go b/test/usecases/odusliceassurance/goversion/main.go new file mode 100644 index 00000000..05b5edf3 --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/main.go @@ -0,0 +1,58 @@ +// - +// ========================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 +} diff --git a/test/usecases/odusliceassurance/goversion/messages/policyRatio.go b/test/usecases/odusliceassurance/goversion/messages/policyRatio.go new file mode 100644 index 00000000..8868ff8b --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/messages/policyRatio.go @@ -0,0 +1,82 @@ +// - +// ========================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, + }, + }, + } +} diff --git a/test/usecases/odusliceassurance/goversion/messages/stdVesMessage.go b/test/usecases/odusliceassurance/goversion/messages/stdVesMessage.go new file mode 100644 index 00000000..1ee0dcf3 --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/messages/stdVesMessage.go @@ -0,0 +1,124 @@ +// - +// ========================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 +} diff --git a/test/usecases/odusliceassurance/goversion/stub/mrproducer/mrstub.go b/test/usecases/odusliceassurance/goversion/stub/mrproducer/mrstub.go new file mode 100644 index 00000000..e7591255 --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/stub/mrproducer/mrstub.go @@ -0,0 +1,130 @@ +// - +// ========================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", + }, +} diff --git a/test/usecases/odusliceassurance/goversion/stub/sdnr/sdnrstub.go b/test/usecases/odusliceassurance/goversion/stub/sdnr/sdnrstub.go new file mode 100644 index 00000000..b8f80be3 --- /dev/null +++ b/test/usecases/odusliceassurance/goversion/stub/sdnr/sdnrstub.go @@ -0,0 +1,145 @@ +// - +// ========================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) +}