require github.com/sirupsen/logrus v1.8.1
-require github.com/gorilla/mux v1.8.0
+require (
+ github.com/gorilla/mux v1.8.0
+ github.com/stretchr/testify v1.3.0
+)
-require golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
+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
+// -
+// ========================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!")
+}
}
}
-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 {
--- /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/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{})
+
+ 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; charset=utf-8", 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: "failed to do request, 400 status code received",
+ },
+ }
+
+ for _, tt := range tests {
+
+ t.Run(tt.name, func(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ 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{})
+ var res interface{}
+ err := client.Get(srv.URL, &res)
+
+ if err != nil {
+ assertions.Equal(tt.wantErr, err.Error())
+ }
+ assertions.Equal(tt.args.resp, res)
+ })
+ }
+}
+
+func TestPost(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 Post request",
+ args: args{
+ header: "application/json",
+ respCode: http.StatusOK,
+ resp: "Success!",
+ },
+ wantErr: "",
+ },
+ }
+
+ 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.MethodPost, r.Method)
+ assertions.Contains(r.Header.Get("Content-Type"), "application/json")
+
+ var reqBody interface{}
+ decoder := json.NewDecoder(r.Body)
+ decoder.Decode(&reqBody)
+ assertions.Equal(reqBody, `json:"example"`)
+
+ 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{})
+ payload := `json:"example"`
+ err := client.Post(srv.URL, payload, nil)
+
+ if err != nil {
+ assertions.Equal(tt.wantErr, err.Error())
+ }
+ })
+ }
+}
)
type App struct {
- client restclient.HTTPClient
+ client *restclient.Client
metricsPolicies *structures.SliceAssuranceMeas
}
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+)\]`)
+ 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]) == 6 {
- duid = res[0][1]
- sd = toInt(res[0][4])
- sst = toInt(res[0][5])
+ 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][3], meas.Value)
+ sa.updateMetric(key, value, res[0][4], meas.Value)
} else {
// Only add new one if value exceeds threshold
sa.addMetric(res, meas.Value)
func (sa *SliceAssuranceMeas) addMetric(res [][]string, metricValue int) {
if metricValue > 700 {
- metric := NewSliceMetric(res[0][1], res[0][2], toInt(res[0][4]), toInt(res[0][5]))
+ 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][1], toInt(res[0][4]), toInt(res[0][5])}
+ key := MapKey{res[0][2], toInt(res[0][5]), toInt(res[0][6])}
sa.Metrics[key] = 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 messages
+
+import (
+ "testing"
+)
+
+func TestGetMeasurements(t *testing.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,
+ }
+ if got := message.GetMeasurements(); len(got) != len(tt.want) {
+ t.Errorf("Message.GetMeasurements() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}