b191db51faafd0b275aacc506fe21fbe87894456
[ric-plt/o1.git] / agent / pkg / nbi / nbi.go
1 /*
2 ==================================================================================
3   Copyright (c) 2019 AT&T Intellectual Property.
4   Copyright (c) 2019 Nokia
5
6    Licensed under the Apache License, Version 2.0 (the "License");
7    you may not use this file except in compliance with the License.
8    You may obtain a copy of the License at
9
10        http://www.apache.org/licenses/LICENSE-2.0
11
12    Unless required by applicable law or agreed to in writing, software
13    distributed under the License is distributed on an "AS IS" BASIS,
14    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15    See the License for the specific language governing permissions and
16    limitations under the License.
17 ==================================================================================
18 */
19
20 package nbi
21
22 import (
23         "encoding/json"
24         "errors"
25         "fmt"
26         "github.com/spf13/viper"
27         "github.com/valyala/fastjson"
28         "os"
29         "strings"
30         "time"
31         "unsafe"
32
33         "gerrit.o-ran-sc.org/r/ric-plt/xapp-frame/pkg/xapp"
34         "gerrit.oran-osc.org/r/ric-plt/o1mediator/pkg/sbi"
35 )
36
37 /*
38 #cgo LDFLAGS: -lsysrepo -lyang
39
40 #include <stdio.h>
41 #include <limits.h>
42 #include <sysrepo.h>
43 #include <sysrepo/values.h>
44 #include "helper.h"
45 */
46 import "C"
47
48 var sbiClient sbi.SBIClientInterface
49 var nbiClient *Nbi
50 var log = xapp.Logger
51 var rnib iRnib = xapp.Rnib
52
53 func NewNbi(s sbi.SBIClientInterface) *Nbi {
54         sbiClient = s
55
56         nbiClient = &Nbi{
57                 schemas:     viper.GetStringSlice("nbi.schemas"),
58                 cleanupChan: make(chan bool),
59         }
60         return nbiClient
61 }
62
63 func (n *Nbi) Start() bool {
64         if ok := n.Setup(n.schemas); !ok {
65                 log.Error("NBI: SYSREPO initialization failed, bailing out!")
66                 return false
67         }
68         log.Info("NBI: SYSREPO initialization done ... processing O1 requests!")
69
70         return true
71 }
72
73 func (n *Nbi) Stop() {
74         C.sr_unsubscribe(n.subscription)
75         C.sr_session_stop(n.session)
76         C.sr_disconnect(n.connection)
77
78         log.Info("NBI: SYSREPO cleanup done gracefully!")
79 }
80
81 func (n *Nbi) Setup(schemas []string) bool {
82         rc := C.sr_connect(0, &n.connection)
83         if C.SR_ERR_OK != rc {
84                 log.Error("NBI: sr_connect failed: %s", C.GoString(C.sr_strerror(rc)))
85                 return false
86         }
87
88         rc = C.sr_session_start(n.connection, C.SR_DS_RUNNING, &n.session)
89         if C.SR_ERR_OK != rc {
90                 log.Error("NBI: sr_session_start failed: %s", C.GoString(C.sr_strerror(rc)))
91                 return false
92         }
93
94         for {
95                 if ok := n.DoSubscription(schemas); ok == true {
96                         break
97                 }
98                 time.Sleep(time.Duration(5 * time.Second))
99         }
100         return true
101 }
102
103 func (n *Nbi) DoSubscription(schemas []string) bool {
104         log.Info("Subscribing YANG modules ... %v", schemas)
105
106         for _, module := range schemas {
107                 modName := C.CString(module)
108                 defer C.free(unsafe.Pointer(modName))
109
110                 if done := n.SubscribeModule(modName); !done {
111                         return false
112                 }
113         }
114         return n.SubscribeStatusData()
115 }
116
117 func (n *Nbi) SubscribeModule(module *C.char) bool {
118         rc := C.sr_module_change_subscribe(n.session, module, nil, C.sr_module_change_cb(C.module_change_cb), nil, 0, 0, &n.subscription)
119         if C.SR_ERR_OK != rc {
120                 log.Info("NBI: sr_module_change_subscribe failed: %s", C.GoString(C.sr_strerror(rc)))
121                 return false
122         }
123         return true
124 }
125
126 func (n *Nbi) SubscribeStatusData() bool {
127         if ok := n.SubscribeStatus("o-ran-sc-ric-gnb-status-v1", "/o-ran-sc-ric-gnb-status-v1:ric/nodes"); !ok {
128                 return ok
129         }
130
131         if ok := n.SubscribeStatus("o-ran-sc-ric-xapp-desc-v1", "/o-ran-sc-ric-xapp-desc-v1:ric/health"); !ok {
132                 return ok
133         }
134
135         if ok := n.SubscribeStatus("o-ran-sc-ric-alarm-v1", "/o-ran-sc-ric-alarm-v1:ric/alarms"); !ok {
136                 return ok
137         }
138
139         return true
140 }
141
142 func (n *Nbi) SubscribeStatus(module, xpath string) bool {
143         mod := C.CString(module)
144         path := C.CString(xpath)
145         defer C.free(unsafe.Pointer(mod))
146         defer C.free(unsafe.Pointer(path))
147
148         rc := C.sr_oper_get_items_subscribe(n.session, mod, path, C.sr_oper_get_items_cb(C.gnb_status_cb), nil, 0, &n.subscription)
149         if C.SR_ERR_OK != rc {
150                 log.Error("NBI: sr_oper_get_items_subscribe failed: %s", C.GoString(C.sr_strerror(rc)))
151                 return false
152         }
153         return true
154 }
155
156 //export nbiModuleChangeCB
157 func nbiModuleChangeCB(session *C.sr_session_ctx_t, module *C.char, xpath *C.char, event C.sr_event_t, reqId C.int) C.int {
158         changedModule := C.GoString(module)
159         changedXpath := C.GoString(xpath)
160
161         log.Info("NBI: change event='%d' module=%s xpath=%s reqId=%d", event, changedModule, changedXpath, reqId)
162         if C.SR_EV_CHANGE != event {
163                 log.Info("NBI: Changes finalized!")
164                 return C.SR_ERR_OK
165         }
166
167         if changedModule == "o-ran-sc-ric-xapp-desc-v1" {
168                 configJson := C.yang_data_sr2json(session, module, event, &nbiClient.oper)
169                 err := nbiClient.ManageXapps(changedModule, C.GoString(configJson), int(nbiClient.oper))
170                 if err != nil {
171                         return C.SR_ERR_OPERATION_FAILED
172                 }
173         }
174
175         if changedModule == "o-ran-sc-ric-ueec-config-v1" {
176                 configJson := C.get_data_json(session, module)
177                 err := nbiClient.ManageConfigmaps(changedModule, C.GoString(configJson), int(C.SR_OP_MODIFIED))
178                 if err != nil {
179                         return C.SR_ERR_OPERATION_FAILED
180                 }
181         }
182
183         return C.SR_ERR_OK
184 }
185
186 func (n *Nbi) ManageXapps(module, configJson string, oper int) error {
187         log.Info("ManageXapps: module=%s configJson=%s", module, configJson)
188
189         if configJson == "" {
190                 return nil
191         }
192
193         root := fmt.Sprintf("%s:ric", module)
194         jsonList, err := n.ParseJsonArray(configJson, root, "xapps", "xapp")
195         if err != nil {
196                 return err
197         }
198
199         for _, m := range jsonList {
200                 xappName := string(m.GetStringBytes("name"))
201                 namespace := string(m.GetStringBytes("namespace"))
202                 relName := string(m.GetStringBytes("release-name"))
203                 version := string(m.GetStringBytes("version"))
204
205                 desc := sbiClient.BuildXappDescriptor(xappName, namespace, relName, version)
206                 switch oper {
207                 case C.SR_OP_CREATED:
208                         return sbiClient.DeployXapp(desc)
209                 case C.SR_OP_DELETED:
210                         return sbiClient.UndeployXapp(desc)
211                 default:
212                         return errors.New(fmt.Sprintf("Operation '%d' not supported!", oper))
213                 }
214         }
215         return nil
216 }
217
218 func (n *Nbi) ManageConfigmaps(module, configJson string, oper int) error {
219         log.Info("ManageConfig: module=%s configJson=%s", module, configJson)
220
221         if configJson == "" {
222                 return nil
223         }
224
225         if oper != C.SR_OP_MODIFIED {
226                 return errors.New(fmt.Sprintf("Operation '%d' not supported!", oper))
227         }
228
229         value, err := n.ParseJson(configJson)
230         if err != nil {
231                 log.Info("ParseJson failed with error: %v", oper)
232                 return err
233         }
234
235         root := fmt.Sprintf("%s:ric", module)
236         appName := string(value.GetStringBytes(root, "config", "name"))
237         namespace := string(value.GetStringBytes(root, "config", "namespace"))
238         controlVal := value.Get(root, "config", "control")
239         if controlVal == nil {
240                 return nil
241         }
242         control := controlVal.String()
243
244         var f interface{}
245         err = json.Unmarshal([]byte(strings.ReplaceAll(control, "\\", "")), &f)
246         if err != nil {
247                 log.Info("json.Unmarshal failed: %v", err)
248                 return err
249         }
250
251         xappConfig := sbiClient.BuildXappConfig(appName, namespace, f)
252         return sbiClient.ModifyXappConfig(xappConfig)
253 }
254
255 func (n *Nbi) ParseJson(dsContent string) (*fastjson.Value, error) {
256         var p fastjson.Parser
257         v, err := p.Parse(dsContent)
258         if err != nil {
259                 log.Info("fastjson.Parser failed: %v", err)
260         }
261         return v, err
262 }
263
264 func (n *Nbi) ParseJsonArray(dsContent, model, top, elem string) ([]*fastjson.Value, error) {
265         v, err := n.ParseJson(dsContent)
266         if err != nil {
267                 return nil, err
268         }
269         return v.GetArray(model, top, elem), nil
270 }
271
272 //export nbiGnbStateCB
273 func nbiGnbStateCB(session *C.sr_session_ctx_t, module *C.char, xpath *C.char, rpath *C.char, reqid C.uint32_t, parent **C.char) C.int {
274         mod := C.GoString(module)
275         log.Info("nbiGnbStateCB: module='%s' xpath='%s' rpath='%s' [id=%d]", mod, C.GoString(xpath), C.GoString(rpath), reqid)
276
277         if mod == "o-ran-sc-ric-xapp-desc-v1" {
278                 xappnamespace := os.Getenv("XAPP_NAMESPACE")
279                 if xappnamespace == "" {
280                         xappnamespace = "ricxapp"
281                 }
282                 podList, _ := sbiClient.GetAllPodStatus(xappnamespace)
283
284                 for _, pod := range podList {
285                         path := fmt.Sprintf("/o-ran-sc-ric-xapp-desc-v1:ric/health/status[name='%s']", pod.Name)
286                         nbiClient.CreateNewElement(session, parent, path, "name", path)
287                         nbiClient.CreateNewElement(session, parent, path, "health", pod.Health)
288                         nbiClient.CreateNewElement(session, parent, path, "status", pod.Status)
289                 }
290                 return C.SR_ERR_OK
291         }
292
293         if mod == "o-ran-sc-ric-alarm-v1" {
294                 if alerts, _ := sbiClient.GetAlerts(); alerts != nil {
295                         for _, alert := range alerts.Payload {
296                                 id := alert.Annotations["alarm_id"]
297                                 path := fmt.Sprintf("/o-ran-sc-ric-alarm-v1:ric/alarms/alarm[alarm-id='%s']", id)
298                                 nbiClient.CreateNewElement(session, parent, path, "alarm-id", id)
299                                 nbiClient.CreateNewElement(session, parent, path, "fault-text", alert.Alert.Labels["alertname"])
300                                 nbiClient.CreateNewElement(session, parent, path, "severity", alert.Alert.Labels["severity"])
301                                 nbiClient.CreateNewElement(session, parent, path, "status", alert.Alert.Labels["status"])
302                                 nbiClient.CreateNewElement(session, parent, path, "additional-info", alert.Annotations["additional_info"])
303                         }
304                 }
305                 return C.SR_ERR_OK
306         }
307
308         gnbs, err := rnib.GetListGnbIds()
309         if err != nil || len(gnbs) == 0 {
310                 log.Info("Rnib.GetListGnbIds() returned elementCount=%d err:%v", len(gnbs), err)
311                 return C.SR_ERR_OK
312         }
313
314         for _, gnb := range gnbs {
315                 ranName := gnb.GetInventoryName()
316                 info, err := rnib.GetNodeb(ranName)
317                 if err != nil {
318                         log.Error("GetNodeb() failed for ranName=%s: %v", ranName, err)
319                         continue
320                 }
321
322                 prot := nbiClient.E2APProt2Str(int(info.E2ApplicationProtocol))
323                 connStat := nbiClient.ConnStatus2Str(int(info.ConnectionStatus))
324                 ntype := nbiClient.NodeType2Str(int(info.NodeType))
325
326                 log.Info("gNB info: %s -> %s %s %s -> %s %s", ranName, prot, connStat, ntype, gnb.GetGlobalNbId().GetPlmnId(), gnb.GetGlobalNbId().GetNbId())
327
328                 path := fmt.Sprintf("/o-ran-sc-ric-gnb-status-v1:ric/nodes/node[ran-name='%s']", ranName)
329                 nbiClient.CreateNewElement(session, parent, path, "ran-name", ranName)
330                 nbiClient.CreateNewElement(session, parent, path, "ip", info.Ip)
331                 nbiClient.CreateNewElement(session, parent, path, "port", fmt.Sprintf("%d", info.Port))
332                 nbiClient.CreateNewElement(session, parent, path, "plmn-id", gnb.GetGlobalNbId().GetPlmnId())
333                 nbiClient.CreateNewElement(session, parent, path, "nb-id", gnb.GetGlobalNbId().GetNbId())
334                 nbiClient.CreateNewElement(session, parent, path, "e2ap-protocol", prot)
335                 nbiClient.CreateNewElement(session, parent, path, "connection-status", connStat)
336                 nbiClient.CreateNewElement(session, parent, path, "node", ntype)
337         }
338         return C.SR_ERR_OK
339 }
340
341 func (n *Nbi) CreateNewElement(session *C.sr_session_ctx_t, parent **C.char, key, name, value string) {
342         basePath := fmt.Sprintf("%s/%s", key, name)
343         log.Info("%s -> %s", basePath, value)
344
345         cPath := C.CString(basePath)
346         defer C.free(unsafe.Pointer(cPath))
347         cValue := C.CString(value)
348         defer C.free(unsafe.Pointer(cValue))
349
350         C.create_new_path(session, parent, cPath, cValue)
351 }
352
353 func (n *Nbi) ConnStatus2Str(connStatus int) string {
354         switch connStatus {
355         case 0:
356                 return "not-specified"
357         case 1:
358                 return "connected"
359         case 2:
360                 return "disconnected"
361         case 3:
362                 return "setup-failed"
363         case 4:
364                 return "connecting"
365         case 5:
366                 return "shutting-down"
367         case 6:
368                 return "shutdown"
369         }
370         return "not-specified"
371 }
372
373 func (n *Nbi) E2APProt2Str(prot int) string {
374         switch prot {
375         case 0:
376                 return "not-specified"
377         case 1:
378                 return "x2-setup-request"
379         case 2:
380                 return "endc-x2-setup-request"
381         }
382         return "not-specified"
383 }
384
385 func (n *Nbi) NodeType2Str(ntype int) string {
386         switch ntype {
387         case 0:
388                 return "not-specified"
389         case 1:
390                 return "enb"
391         case 2:
392                 return "gnb"
393         }
394         return "not-specified"
395 }
396
397 func (n *Nbi) testModuleChangeCB(module string) bool {
398         var event C.sr_event_t = C.SR_EV_CHANGE
399         reqID := C.int(100)
400         modName := C.CString(module)
401         defer C.free(unsafe.Pointer(modName))
402
403         if ret := nbiModuleChangeCB(n.session, modName, nil, event, reqID); ret != C.SR_ERR_OK {
404                 return false
405         }
406         return true
407 }
408
409 func (n *Nbi) testModuleChangeCBDone(module string) bool {
410         var event C.sr_event_t = C.SR_EV_DONE
411         reqID := C.int(100)
412         modName := C.CString(module)
413         defer C.free(unsafe.Pointer(modName))
414
415         if ret := nbiModuleChangeCB(n.session, modName, nil, event, reqID); ret != C.SR_ERR_OK {
416                 return false
417         }
418         return true
419 }
420
421 func (n *Nbi) testGnbStateCB(module string) bool {
422         modName := C.CString(module)
423         defer C.free(unsafe.Pointer(modName))
424         reqID := C.uint32_t(100)
425         parent := make([]*C.char, 1)
426
427         if ret := nbiGnbStateCB(n.session, modName, nil, nil, reqID, &parent[0]); ret != C.SR_ERR_OK {
428                 return false
429         }
430         return true
431 }
432
433 type iRnib interface {
434         GetListGnbIds() ([]*xapp.RNIBNbIdentity, xapp.RNIBIRNibError)
435         GetNodeb(invName string) (*xapp.RNIBNodebInfo, xapp.RNIBIRNibError)
436 }