Improvement of 'Get Gnb Status’ command to fetch Enb details in O1 NETCONF client
[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         log.Info("Rnib.GetListGnbIds() returned elementCount=%d err:%v", len(gnbs), err)
310         if err == nil && len(gnbs) > 0 {
311                 for _, gnb := range gnbs {
312                         ranName := gnb.GetInventoryName()
313                         info, err := rnib.GetNodeb(ranName)
314                         if err != nil {
315                                 log.Error("GetNodeb() failed for ranName=%s: %v", ranName, err)
316                                 continue
317                         }
318
319                         prot := nbiClient.E2APProt2Str(int(info.E2ApplicationProtocol))
320                         connStat := nbiClient.ConnStatus2Str(int(info.ConnectionStatus))
321                         ntype := nbiClient.NodeType2Str(int(info.NodeType))
322
323                         log.Info("gNB info: %s -> %s %s %s -> %s %s", ranName, prot, connStat, ntype, gnb.GetGlobalNbId().GetPlmnId(), gnb.GetGlobalNbId().GetNbId())
324
325                         path := fmt.Sprintf("/o-ran-sc-ric-gnb-status-v1:ric/nodes/node[ran-name='%s']", ranName)
326                         nbiClient.CreateNewElement(session, parent, path, "ran-name", ranName)
327                         nbiClient.CreateNewElement(session, parent, path, "ip", info.Ip)
328                         nbiClient.CreateNewElement(session, parent, path, "port", fmt.Sprintf("%d", info.Port))
329                         nbiClient.CreateNewElement(session, parent, path, "plmn-id", gnb.GetGlobalNbId().GetPlmnId())
330                         nbiClient.CreateNewElement(session, parent, path, "nb-id", gnb.GetGlobalNbId().GetNbId())
331                         nbiClient.CreateNewElement(session, parent, path, "e2ap-protocol", prot)
332                         nbiClient.CreateNewElement(session, parent, path, "connection-status", connStat)
333                         nbiClient.CreateNewElement(session, parent, path, "node", ntype)
334                 }
335         }
336
337         //Check if any Enbs are connected to RIC
338         enbs, err2 := rnib.GetListEnbIds()
339         log.Info("Rnib.GetListEnbIds() returned elementCount=%d err:%v", len(gnbs), err)
340         if err2 == nil || len(enbs) > 0 {
341                 log.Info("Getting Enb details from list of Enbs")
342                 for _, enb := range enbs {
343                         ranName := enb.GetInventoryName()
344                         info, err := rnib.GetNodeb(ranName)
345                         if err != nil {
346                                 log.Error("GetNodeb() failed for ranName=%s: %v", ranName, err)
347                                 continue
348                         }
349
350                         prot := nbiClient.E2APProt2Str(int(info.E2ApplicationProtocol))
351                         connStat := nbiClient.ConnStatus2Str(int(info.ConnectionStatus))
352                         ntype := nbiClient.NodeType2Str(int(info.NodeType))
353
354                         log.Info("eNB info: %s -> %s %s %s -> %s %s", ranName, prot, connStat, ntype, enb.GetGlobalNbId().GetPlmnId(), enb.GetGlobalNbId().GetNbId())
355
356                         path := fmt.Sprintf("/o-ran-sc-ric-gnb-status-v1:ric/nodes/node[ran-name='%s']", ranName)
357                         nbiClient.CreateNewElement(session, parent, path, "ran-name", ranName)
358                         nbiClient.CreateNewElement(session, parent, path, "ip", info.Ip)
359                         nbiClient.CreateNewElement(session, parent, path, "port", fmt.Sprintf("%d", info.Port))
360                         nbiClient.CreateNewElement(session, parent, path, "plmn-id", enb.GetGlobalNbId().GetPlmnId())
361                         nbiClient.CreateNewElement(session, parent, path, "nb-id", enb.GetGlobalNbId().GetNbId())
362                         nbiClient.CreateNewElement(session, parent, path, "e2ap-protocol", prot)
363                         nbiClient.CreateNewElement(session, parent, path, "connection-status", connStat)
364                         nbiClient.CreateNewElement(session, parent, path, "node", ntype)
365                 }
366         }
367
368         return C.SR_ERR_OK
369 }
370
371 func (n *Nbi) CreateNewElement(session *C.sr_session_ctx_t, parent **C.char, key, name, value string) {
372         basePath := fmt.Sprintf("%s/%s", key, name)
373         log.Info("%s -> %s", basePath, value)
374
375         cPath := C.CString(basePath)
376         defer C.free(unsafe.Pointer(cPath))
377         cValue := C.CString(value)
378         defer C.free(unsafe.Pointer(cValue))
379
380         C.create_new_path(session, parent, cPath, cValue)
381 }
382
383 func (n *Nbi) ConnStatus2Str(connStatus int) string {
384         switch connStatus {
385         case 0:
386                 return "not-specified"
387         case 1:
388                 return "connected"
389         case 2:
390                 return "disconnected"
391         case 3:
392                 return "setup-failed"
393         case 4:
394                 return "connecting"
395         case 5:
396                 return "shutting-down"
397         case 6:
398                 return "shutdown"
399         }
400         return "not-specified"
401 }
402
403 func (n *Nbi) E2APProt2Str(prot int) string {
404         switch prot {
405         case 0:
406                 return "not-specified"
407         case 1:
408                 return "x2-setup-request"
409         case 2:
410                 return "endc-x2-setup-request"
411         }
412         return "not-specified"
413 }
414
415 func (n *Nbi) NodeType2Str(ntype int) string {
416         switch ntype {
417         case 0:
418                 return "not-specified"
419         case 1:
420                 return "enb"
421         case 2:
422                 return "gnb"
423         }
424         return "not-specified"
425 }
426
427 func (n *Nbi) testModuleChangeCB(module string) bool {
428         var event C.sr_event_t = C.SR_EV_CHANGE
429         reqID := C.int(100)
430         modName := C.CString(module)
431         defer C.free(unsafe.Pointer(modName))
432
433         if ret := nbiModuleChangeCB(n.session, modName, nil, event, reqID); ret != C.SR_ERR_OK {
434                 return false
435         }
436         return true
437 }
438
439 func (n *Nbi) testModuleChangeCBDone(module string) bool {
440         var event C.sr_event_t = C.SR_EV_DONE
441         reqID := C.int(100)
442         modName := C.CString(module)
443         defer C.free(unsafe.Pointer(modName))
444
445         if ret := nbiModuleChangeCB(n.session, modName, nil, event, reqID); ret != C.SR_ERR_OK {
446                 return false
447         }
448         return true
449 }
450
451 func (n *Nbi) testGnbStateCB(module string) bool {
452         modName := C.CString(module)
453         defer C.free(unsafe.Pointer(modName))
454         reqID := C.uint32_t(100)
455         parent := make([]*C.char, 1)
456
457         if ret := nbiGnbStateCB(n.session, modName, nil, nil, reqID, &parent[0]); ret != C.SR_ERR_OK {
458                 return false
459         }
460         return true
461 }
462
463 type iRnib interface {
464         GetListGnbIds() ([]*xapp.RNIBNbIdentity, xapp.RNIBIRNibError)
465         GetListEnbIds() ([]*xapp.RNIBNbIdentity, xapp.RNIBIRNibError)
466         GetNodeb(invName string) (*xapp.RNIBNodebInfo, xapp.RNIBIRNibError)
467 }