2 // ========================LICENSE_START=================================
5 // Copyright (C) 2021: Nordix Foundation
7 // Licensed under the Apache License, Version 2.0 (the "License");
8 // you may not use this file except in compliance with the License.
9 // You may obtain a copy of the License at
11 // http://www.apache.org/licenses/LICENSE-2.0
13 // Unless required by applicable law or agreed to in writing, software
14 // distributed under the License is distributed on an "AS IS" BASIS,
15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 // See the License for the specific language governing permissions and
17 // limitations under the License.
18 // ========================LICENSE_END===================================
35 "github.com/gorilla/mux"
36 "oransc.org/usecase/oduclosedloop/messages"
38 log "github.com/sirupsen/logrus"
41 const THRESHOLD_TPUT int = 7000
43 type SliceAssuranceInformation struct {
53 policyDedicatedRatio int
56 var data []*SliceAssuranceInformation
57 var messagesToSend []messages.Measurement
60 lines, err := GetCsvFromFile("test-data.csv")
64 for _, line := range lines {
65 sai := SliceAssuranceInformation{
71 metricValue: toInt(line[5]),
72 policyRatioId: line[6],
73 policyMaxRatio: toInt(line[7]),
74 policyMinRatio: toInt(line[8]),
75 policyDedicatedRatio: toInt(line[9]),
77 data = append(data, &sai)
81 func GetCsvFromFile(name string) ([][]string, error) {
82 if csvFile, err := os.Open(name); err == nil {
84 reader := csv.NewReader(csvFile)
85 reader.FieldsPerRecord = -1
86 if csvData, err := reader.ReadAll(); err == nil {
96 func toInt(num string) int {
97 res, err := strconv.Atoi(num)
105 rand.Seed(time.Now().UnixNano())
107 portSdnr := flag.Int("sdnr-port", 3904, "The port this SDNR stub will listen on")
108 portDmaapMR := flag.Int("dmaap-port", 3905, "The port this Dmaap message router will listen on")
113 wg := new(sync.WaitGroup)
119 r.HandleFunc("/rests/data/network-topology:network-topology/topology=topology-netconf/node={NODE-ID}/yang-ext:mount/o-ran-sc-du-hello-world:network-function/distributed-unit-functions={O-DU-ID}", getSdnrResponseMessage).Methods(http.MethodGet)
120 r.HandleFunc("/rests/data/network-topology:network-topology/topology=topology-netconf/node={NODE-ID}/yang-ext:mount/o-ran-sc-du-hello-world:network-function/distributed-unit-functions={O-DU-ID}/radio-resource-management-policy-ratio={POLICY-ID}", updateRRMPolicyDedicatedRatio).Methods(http.MethodPut)
122 fmt.Println("Starting SDNR stub on port: ", *portSdnr)
124 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", *portSdnr), r))
131 r.HandleFunc("/events/unauthenticated.VES_O_RAN_SC_HELLO_WORLD_PM_STREAMING_OUTPUT/myG/C1", sendDmaapMRMessages).Methods(http.MethodGet)
133 fmt.Println("Starting DmaapMR stub on port: ", *portDmaapMR)
135 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", *portDmaapMR), r))
142 func getSdnrResponseMessage(w http.ResponseWriter, r *http.Request) {
144 log.Info("Get messages for RRM Policy Ratio information for O-Du ID ", vars["O-DU-ID"])
146 distUnitFunctions := getDistributedUnitFunctionMessage(vars["O-DU-ID"])
148 respondWithJSON(w, http.StatusOK, distUnitFunctions)
151 func getDistributedUnitFunctionMessage(oduId string) messages.ORanDuRestConf {
153 var policies []messages.RRMPolicyRatio
154 keys := make(map[string]bool)
155 for _, entry := range data {
156 if _, value := keys[entry.policyRatioId]; !value {
157 keys[entry.policyRatioId] = true
158 message := messages.RRMPolicyRatio{
160 Id: entry.policyRatioId,
162 UserLabel: entry.policyRatioId,
163 RRMPolicyMaxRatio: entry.policyMaxRatio,
164 RRMPolicyMinRatio: entry.policyMinRatio,
165 RRMPolicyDedicatedRatio: entry.policyDedicatedRatio,
167 RRMPolicyMembers: []messages.RRMPolicyMember{
169 MobileCountryCode: "310",
170 MobileNetworkCode: "150",
171 SliceDifferentiator: entry.sd,
172 SliceServiceType: entry.sst,
176 policies = append(policies, message)
180 var publicLandMobileNetworks []messages.PublicLandMobileNetworks
181 for _, entry := range data {
182 publicLandMobileNetwork := messages.PublicLandMobileNetworks{
183 MobileCountryCode: "310",
184 MobileNetworkCode: "150",
185 SliceDifferentiator: entry.sd,
186 SliceServiceType: entry.sst,
188 publicLandMobileNetworks = append(publicLandMobileNetworks, publicLandMobileNetwork)
191 var supportedSnssaiSubcounterInstances []messages.SupportedSnssaiSubcounterInstances
192 for _, entry := range data {
193 supportedSnssaiSubcounterInstance := messages.SupportedSnssaiSubcounterInstances{
194 SliceDifferentiator: entry.sd,
195 SliceServiceType: entry.sst,
197 supportedSnssaiSubcounterInstances = append(supportedSnssaiSubcounterInstances, supportedSnssaiSubcounterInstance)
200 cell := messages.Cell{
204 BaseStationChannelBandwidth: messages.BaseStationChannelBandwidth{
207 SupplementaryUplink: 84000,
209 OperationalState: "enabled",
210 TrackingAreaCode: 10,
211 AdmState: "unlocked",
212 PublicLandMobileNetworks: publicLandMobileNetworks,
213 SupportedMeasurements: []messages.SupportedMeasurements{
215 PerformanceMeasurementType: "o-ran-sc-du-hello-world:user-equipment-average-throughput-uplink",
216 SupportedSnssaiSubcounterInstances: supportedSnssaiSubcounterInstances,
219 PerformanceMeasurementType: "o-ran-sc-du-hello-world:user-equipment-average-throughput-downlink",
220 SupportedSnssaiSubcounterInstances: supportedSnssaiSubcounterInstances,
223 TrafficState: "active",
224 AbsoluteRadioFrequencyChannelNumber: messages.AbsoluteRadioFrequencyChannelNumber{
227 SupplementaryUplink: 14500,
230 SynchronizationSignalBlock: messages.SynchronizationSignalBlock{
232 FrequencyChannelNumber: 12,
234 SubcarrierSpacing: 30,
239 distUnitFunction := messages.DistributedUnitFunction{
241 OperationalState: "enabled",
242 AdmState: "unlocked",
244 Cell: []messages.Cell{
247 RRMPolicyRatio: policies,
250 duRRMPolicyRatio := messages.ORanDuRestConf{
251 DistributedUnitFunction: []messages.DistributedUnitFunction{
256 return duRRMPolicyRatio
259 func updateRRMPolicyDedicatedRatio(w http.ResponseWriter, r *http.Request) {
260 var policies struct {
261 RRMPolicies []messages.RRMPolicyRatio `json:"radio-resource-management-policy-ratio"`
263 decoder := json.NewDecoder(r.Body)
265 if err := decoder.Decode(&policies); err != nil {
266 respondWithError(w, http.StatusBadRequest, "Invalid request payload")
271 prMessages := policies.RRMPolicies
272 log.Infof("Post request to update RRMPolicyDedicatedRatio %+v", prMessages)
273 findAndUpdatePolicy(prMessages)
274 respondWithJSON(w, http.StatusOK, map[string]string{"status": "200"})
277 func findAndUpdatePolicy(rRMPolicyRatio []messages.RRMPolicyRatio) {
278 for _, policy := range rRMPolicyRatio {
279 for _, entry := range data {
280 if entry.policyRatioId == policy.Id {
281 log.Infof("update Policy Dedicated Ratio: value for policy %+v\n Old value: %v New value: %v ", policy, entry.policyDedicatedRatio, policy.RRMPolicyDedicatedRatio)
282 entry.policyDedicatedRatio = policy.RRMPolicyDedicatedRatio
283 if entry.metricValue > THRESHOLD_TPUT {
284 entry.metricValue = rand.Intn(THRESHOLD_TPUT)
286 messagesToSend = append(messagesToSend, generateMeasurementEntry(entry))
292 func respondWithError(w http.ResponseWriter, code int, message string) {
293 respondWithJSON(w, code, map[string]string{"error": message})
296 func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
297 response, _ := json.Marshal(payload)
299 w.Header().Set("Content-Type", "application/json")
304 func sendDmaapMRMessages(w http.ResponseWriter, r *http.Request) {
305 log.Info("Send Dmaap messages")
306 entry := data[rand.Intn(5)]
308 maxTput := THRESHOLD_TPUT + 100
309 randomTput := rand.Intn(maxTput-THRESHOLD_TPUT+1) + THRESHOLD_TPUT
310 if randomTput%3 == 0 {
311 log.Info("Using tput value higher than THRESHOLD_TPUT ", randomTput)
312 entry.metricValue = randomTput
314 randomEventId := rand.Intn(10000)
315 messagesToSend = append(messagesToSend, generateMeasurementEntry(entry))
317 message := messages.StdDefinedMessage{
318 Event: messages.Event{
319 CommonEventHeader: messages.CommonEventHeader{
320 Domain: "stndDefined",
321 EventId: "pm-1_16442" + strconv.Itoa(randomEventId),
322 EventName: "stndDefined_performanceMeasurementStreaming",
323 EventType: "performanceMeasurementStreaming",
326 ReportingEntityId: "",
327 ReportingEntityName: "O-DU-1122",
329 SourceName: "O-DU-1122",
330 StartEpochMicrosec: 1644252450000000,
331 LastEpochMicrosec: 1644252480000000,
332 NfNamingCode: "SIM-O-DU",
333 NfVendorName: "O-RAN-SC SIM Project",
334 StndDefinedNamespace: "o-ran-sc-du-hello-world-pm-streaming-oas3",
335 TimeZoneOffset: "+00:00",
337 VesEventListenerVersion: "7.2.1",
339 StndDefinedFields: messages.StndDefinedFields{
340 StndDefinedFieldsVersion: "1.0",
341 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",
343 DataId: "pm-1_1644252450",
344 StartTime: "2022-02-07T16:47:30.0Z",
345 AdministrativeState: "unlocked",
346 OperationalState: "enabled",
348 JobTag: "my-job-tag",
349 GranularityPeriod: 30,
350 Measurements: messagesToSend,
356 fmt.Printf("Sending Dmaap message:\n %+v\n", message)
358 messageAsByteArray, _ := json.Marshal(message)
359 response := [1]string{string(messageAsByteArray)}
361 time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
362 respondWithJSON(w, http.StatusOK, response)
367 func generateMeasurementEntry(entry *SliceAssuranceInformation) messages.Measurement {
369 measurementTypeInstanceReference := "/o-ran-sc-du-hello-world:network-function/distributed-unit-functions[id='" + entry.duId + "']/cell[id='" + entry.cellId + "']/supported-measurements[performance-measurement-type='(urn:o-ran-sc:yang:o-ran-sc-du-hello-world?revision=2021-11-23)" + entry.metricName + "']/supported-snssai-subcounter-instances[slice-differentiator='" + strconv.Itoa(entry.sd) + "'][slice-service-type='" + strconv.Itoa(entry.sst) + "']"
370 meas := messages.Measurement{
372 MeasurementTypeInstanceReference: measurementTypeInstanceReference,
373 Value: entry.metricValue,