Seed code for O-DU slice assurance new repo
[nonrtric/rapp/ransliceassurance.git] / smoversion / stub / simulator.go
1 // -
2 //   ========================LICENSE_START=================================
3 //   O-RAN-SC
4 //   %%
5 //   Copyright (C) 2021: Nordix Foundation
6 //   %%
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
10 //
11 //        http://www.apache.org/licenses/LICENSE-2.0
12 //
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===================================
19 //
20
21 package main
22
23 import (
24         "encoding/csv"
25         "encoding/json"
26         "flag"
27         "fmt"
28         "math/rand"
29         "net/http"
30         "os"
31         "strconv"
32         "sync"
33         "time"
34
35         "github.com/gorilla/mux"
36         "oransc.org/usecase/oduclosedloop/messages"
37
38         log "github.com/sirupsen/logrus"
39 )
40
41 const THRESHOLD_TPUT int = 7000
42
43 type SliceAssuranceInformation struct {
44         duId                 string
45         cellId               string
46         sd                   int
47         sst                  int
48         metricName           string
49         metricValue          int
50         policyRatioId        string
51         policyMaxRatio       int
52         policyMinRatio       int
53         policyDedicatedRatio int
54 }
55
56 var data []*SliceAssuranceInformation
57 var messagesToSend []messages.Measurement
58
59 func loadData() {
60         lines, err := GetCsvFromFile("test-data.csv")
61         if err != nil {
62                 panic(err)
63         }
64         for _, line := range lines {
65                 sai := SliceAssuranceInformation{
66                         duId:                 line[0],
67                         cellId:               line[1],
68                         sd:                   toInt(line[2]),
69                         sst:                  toInt(line[3]),
70                         metricName:           line[4],
71                         metricValue:          toInt(line[5]),
72                         policyRatioId:        line[6],
73                         policyMaxRatio:       toInt(line[7]),
74                         policyMinRatio:       toInt(line[8]),
75                         policyDedicatedRatio: toInt(line[9]),
76                 }
77                 data = append(data, &sai)
78         }
79 }
80
81 func GetCsvFromFile(name string) ([][]string, error) {
82         if csvFile, err := os.Open(name); err == nil {
83                 defer csvFile.Close()
84                 reader := csv.NewReader(csvFile)
85                 reader.FieldsPerRecord = -1
86                 if csvData, err := reader.ReadAll(); err == nil {
87                         return csvData, nil
88                 } else {
89                         return nil, err
90                 }
91         } else {
92                 return nil, err
93         }
94 }
95
96 func toInt(num string) int {
97         res, err := strconv.Atoi(num)
98         if err != nil {
99                 return -1
100         }
101         return res
102 }
103
104 func main() {
105         rand.Seed(time.Now().UnixNano())
106
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")
109         flag.Parse()
110
111         loadData()
112
113         wg := new(sync.WaitGroup)
114         wg.Add(2)
115
116         go func() {
117
118                 r := mux.NewRouter()
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)
121
122                 fmt.Println("Starting SDNR stub on port: ", *portSdnr)
123
124                 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", *portSdnr), r))
125                 wg.Done()
126         }()
127
128         go func() {
129
130                 r := mux.NewRouter()
131                 r.HandleFunc("/events/unauthenticated.VES_O_RAN_SC_HELLO_WORLD_PM_STREAMING_OUTPUT/myG/C1", sendDmaapMRMessages).Methods(http.MethodGet)
132
133                 fmt.Println("Starting DmaapMR stub on port: ", *portDmaapMR)
134
135                 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", *portDmaapMR), r))
136                 wg.Done()
137         }()
138
139         wg.Wait()
140 }
141
142 func getSdnrResponseMessage(w http.ResponseWriter, r *http.Request) {
143         vars := mux.Vars(r)
144         log.Info("Get messages for RRM Policy Ratio information for O-Du ID ", vars["O-DU-ID"])
145
146         distUnitFunctions := getDistributedUnitFunctionMessage(vars["O-DU-ID"])
147
148         respondWithJSON(w, http.StatusOK, distUnitFunctions)
149 }
150
151 func getDistributedUnitFunctionMessage(oduId string) messages.ORanDuRestConf {
152
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{
159
160                                 Id:                      entry.policyRatioId,
161                                 AdmState:                "locked",
162                                 UserLabel:               entry.policyRatioId,
163                                 RRMPolicyMaxRatio:       entry.policyMaxRatio,
164                                 RRMPolicyMinRatio:       entry.policyMinRatio,
165                                 RRMPolicyDedicatedRatio: entry.policyDedicatedRatio,
166                                 ResourceType:            "prb",
167                                 RRMPolicyMembers: []messages.RRMPolicyMember{
168                                         {
169                                                 MobileCountryCode:   "310",
170                                                 MobileNetworkCode:   "150",
171                                                 SliceDifferentiator: entry.sd,
172                                                 SliceServiceType:    entry.sst,
173                                         },
174                                 },
175                         }
176                         policies = append(policies, message)
177                 }
178         }
179
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,
187                 }
188                 publicLandMobileNetworks = append(publicLandMobileNetworks, publicLandMobileNetwork)
189         }
190
191         var supportedSnssaiSubcounterInstances []messages.SupportedSnssaiSubcounterInstances
192         for _, entry := range data {
193                 supportedSnssaiSubcounterInstance := messages.SupportedSnssaiSubcounterInstances{
194                         SliceDifferentiator: entry.sd,
195                         SliceServiceType:    entry.sst,
196                 }
197                 supportedSnssaiSubcounterInstances = append(supportedSnssaiSubcounterInstances, supportedSnssaiSubcounterInstance)
198         }
199
200         cell := messages.Cell{
201                 Id:             "cell-1",
202                 LocalId:        1,
203                 PhysicalCellId: 1,
204                 BaseStationChannelBandwidth: messages.BaseStationChannelBandwidth{
205                         Uplink:              83000,
206                         Downlink:            80000,
207                         SupplementaryUplink: 84000,
208                 },
209                 OperationalState:         "enabled",
210                 TrackingAreaCode:         10,
211                 AdmState:                 "unlocked",
212                 PublicLandMobileNetworks: publicLandMobileNetworks,
213                 SupportedMeasurements: []messages.SupportedMeasurements{
214                         {
215                                 PerformanceMeasurementType:         "o-ran-sc-du-hello-world:user-equipment-average-throughput-uplink",
216                                 SupportedSnssaiSubcounterInstances: supportedSnssaiSubcounterInstances,
217                         },
218                         {
219                                 PerformanceMeasurementType:         "o-ran-sc-du-hello-world:user-equipment-average-throughput-downlink",
220                                 SupportedSnssaiSubcounterInstances: supportedSnssaiSubcounterInstances,
221                         },
222                 },
223                 TrafficState: "active",
224                 AbsoluteRadioFrequencyChannelNumber: messages.AbsoluteRadioFrequencyChannelNumber{
225                         Uplink:              14000,
226                         Downlink:            15000,
227                         SupplementaryUplink: 14500,
228                 },
229                 UserLabel: "cell-1",
230                 SynchronizationSignalBlock: messages.SynchronizationSignalBlock{
231                         Duration:               2,
232                         FrequencyChannelNumber: 12,
233                         Periodicity:            10,
234                         SubcarrierSpacing:      30,
235                         Offset:                 3,
236                 },
237         }
238
239         distUnitFunction := messages.DistributedUnitFunction{
240                 Id:               oduId,
241                 OperationalState: "enabled",
242                 AdmState:         "unlocked",
243                 UserLabel:        oduId,
244                 Cell: []messages.Cell{
245                         cell,
246                 },
247                 RRMPolicyRatio: policies,
248         }
249
250         duRRMPolicyRatio := messages.ORanDuRestConf{
251                 DistributedUnitFunction: []messages.DistributedUnitFunction{
252                         distUnitFunction,
253                 },
254         }
255
256         return duRRMPolicyRatio
257 }
258
259 func updateRRMPolicyDedicatedRatio(w http.ResponseWriter, r *http.Request) {
260         var policies struct {
261                 RRMPolicies []messages.RRMPolicyRatio `json:"radio-resource-management-policy-ratio"`
262         }
263         decoder := json.NewDecoder(r.Body)
264
265         if err := decoder.Decode(&policies); err != nil {
266                 respondWithError(w, http.StatusBadRequest, "Invalid request payload")
267                 return
268         }
269         defer r.Body.Close()
270
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"})
275 }
276
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)
285                                 }
286                                 messagesToSend = append(messagesToSend, generateMeasurementEntry(entry))
287                         }
288                 }
289         }
290 }
291
292 func respondWithError(w http.ResponseWriter, code int, message string) {
293         respondWithJSON(w, code, map[string]string{"error": message})
294 }
295
296 func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
297         response, _ := json.Marshal(payload)
298
299         w.Header().Set("Content-Type", "application/json")
300         w.WriteHeader(code)
301         w.Write(response)
302 }
303
304 func sendDmaapMRMessages(w http.ResponseWriter, r *http.Request) {
305         log.Info("Send Dmaap messages")
306         entry := data[rand.Intn(5)]
307
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
313         }
314         randomEventId := rand.Intn(10000)
315         messagesToSend = append(messagesToSend, generateMeasurementEntry(entry))
316
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",
324                                 Sequence:                825,
325                                 Priority:                "Low",
326                                 ReportingEntityId:       "",
327                                 ReportingEntityName:     "O-DU-1122",
328                                 SourceId:                "",
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",
336                                 Version:                 "4.1",
337                                 VesEventListenerVersion: "7.2.1",
338                         },
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",
342                                 Data: messages.Data{
343                                         DataId:              "pm-1_1644252450",
344                                         StartTime:           "2022-02-07T16:47:30.0Z",
345                                         AdministrativeState: "unlocked",
346                                         OperationalState:    "enabled",
347                                         UserLabel:           "pm",
348                                         JobTag:              "my-job-tag",
349                                         GranularityPeriod:   30,
350                                         Measurements:        messagesToSend,
351                                 },
352                         },
353                 },
354         }
355
356         fmt.Printf("Sending Dmaap message:\n %+v\n", message)
357
358         messageAsByteArray, _ := json.Marshal(message)
359         response := [1]string{string(messageAsByteArray)}
360
361         time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
362         respondWithJSON(w, http.StatusOK, response)
363
364         messagesToSend = nil
365 }
366
367 func generateMeasurementEntry(entry *SliceAssuranceInformation) messages.Measurement {
368
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{
371
372                 MeasurementTypeInstanceReference: measurementTypeInstanceReference,
373                 Value:                            entry.metricValue,
374                 Unit:                             "kbit/s",
375         }
376         return meas
377 }