Support for NETCONF Get command to get xapp configuration in O1 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         if ok := n.SubscribeStatus("o-ran-sc-ric-xapp-desc-v1", "/o-ran-sc-ric-xapp-desc-v1:ric/configuration"); !ok {
139                 return ok
140         }
141
142         return true
143 }
144
145 func (n *Nbi) SubscribeStatus(module, xpath string) bool {
146         mod := C.CString(module)
147         path := C.CString(xpath)
148         defer C.free(unsafe.Pointer(mod))
149         defer C.free(unsafe.Pointer(path))
150
151         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)
152         if C.SR_ERR_OK != rc {
153                 log.Error("NBI: sr_oper_get_items_subscribe failed: %s", C.GoString(C.sr_strerror(rc)))
154                 return false
155         }
156         return true
157 }
158
159 //export nbiModuleChangeCB
160 func nbiModuleChangeCB(session *C.sr_session_ctx_t, module *C.char, xpath *C.char, event C.sr_event_t, reqId C.int) C.int {
161         changedModule := C.GoString(module)
162         changedXpath := C.GoString(xpath)
163
164         log.Info("NBI: change event='%d' module=%s xpath=%s reqId=%d", event, changedModule, changedXpath, reqId)
165         if C.SR_EV_CHANGE != event {
166                 log.Info("NBI: Changes finalized!")
167                 return C.SR_ERR_OK
168         }
169
170         if changedModule == "o-ran-sc-ric-xapp-desc-v1" {
171                 configJson := C.yang_data_sr2json(session, module, event, &nbiClient.oper)
172                 err := nbiClient.ManageXapps(changedModule, C.GoString(configJson), int(nbiClient.oper))
173                 if err != nil {
174                         return C.SR_ERR_OPERATION_FAILED
175                 }
176         }
177
178         if changedModule == "o-ran-sc-ric-ueec-config-v1" {
179                 configJson := C.get_data_json(session, module)
180                 err := nbiClient.ManageConfigmaps(changedModule, C.GoString(configJson), int(C.SR_OP_MODIFIED))
181                 if err != nil {
182                         return C.SR_ERR_OPERATION_FAILED
183                 }
184         }
185
186         return C.SR_ERR_OK
187 }
188
189 func (n *Nbi) ManageXapps(module, configJson string, oper int) error {
190         log.Info("ManageXapps: module=%s configJson=%s", module, configJson)
191
192         if configJson == "" {
193                 return nil
194         }
195
196         root := fmt.Sprintf("%s:ric", module)
197         jsonList, err := n.ParseJsonArray(configJson, root, "xapps", "xapp")
198         if err != nil {
199                 return err
200         }
201
202         for _, m := range jsonList {
203                 xappName := string(m.GetStringBytes("name"))
204                 namespace := string(m.GetStringBytes("namespace"))
205                 relName := string(m.GetStringBytes("release-name"))
206                 version := string(m.GetStringBytes("version"))
207
208                 desc := sbiClient.BuildXappDescriptor(xappName, namespace, relName, version)
209                 switch oper {
210                 case C.SR_OP_CREATED:
211                         return sbiClient.DeployXapp(desc)
212                 case C.SR_OP_DELETED:
213                         return sbiClient.UndeployXapp(desc)
214                 default:
215                         return errors.New(fmt.Sprintf("Operation '%d' not supported!", oper))
216                 }
217         }
218         return nil
219 }
220
221 func (n *Nbi) ManageConfigmaps(module, configJson string, oper int) error {
222         log.Info("ManageConfig: module=%s configJson=%s", module, configJson)
223
224         if configJson == "" {
225                 return nil
226         }
227
228         if oper != C.SR_OP_MODIFIED {
229                 return errors.New(fmt.Sprintf("Operation '%d' not supported!", oper))
230         }
231
232         value, err := n.ParseJson(configJson)
233         if err != nil {
234                 log.Info("ParseJson failed with error: %v", oper)
235                 return err
236         }
237
238         root := fmt.Sprintf("%s:ric", module)
239         appName := string(value.GetStringBytes(root, "config", "name"))
240         namespace := string(value.GetStringBytes(root, "config", "namespace"))
241         controlVal := value.Get(root, "config", "control")
242         if controlVal == nil {
243                 return nil
244         }
245         control := controlVal.String()
246
247         var f interface{}
248         err = json.Unmarshal([]byte(strings.ReplaceAll(control, "\\", "")), &f)
249         if err != nil {
250                 log.Info("json.Unmarshal failed: %v", err)
251                 return err
252         }
253
254         xappConfig := sbiClient.BuildXappConfig(appName, namespace, f)
255         return sbiClient.ModifyXappConfig(xappConfig)
256 }
257
258 func (n *Nbi) ParseJson(dsContent string) (*fastjson.Value, error) {
259         var p fastjson.Parser
260         v, err := p.Parse(dsContent)
261         if err != nil {
262                 log.Info("fastjson.Parser failed: %v", err)
263         }
264         return v, err
265 }
266
267 func (n *Nbi) ParseJsonArray(dsContent, model, top, elem string) ([]*fastjson.Value, error) {
268         v, err := n.ParseJson(dsContent)
269         if err != nil {
270                 return nil, err
271         }
272         return v.GetArray(model, top, elem), nil
273 }
274
275 //export nbiGnbStateCB
276 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 {
277         mod := C.GoString(module)
278         log.Info("nbiGnbStateCB: module='%s' xpath='%s' rpath='%s' [id=%d]", mod, C.GoString(xpath), C.GoString(rpath), reqid)
279
280         if mod == "o-ran-sc-ric-xapp-desc-v1" {
281
282                 if C.GoString(xpath) == "/o-ran-sc-ric-xapp-desc-v1:ric/configuration" {
283                         nbGetAllXappsDefCfg(session, parent)
284                         return C.SR_ERR_OK
285                 }
286
287                 xappnamespace := os.Getenv("XAPP_NAMESPACE")
288                 if xappnamespace == "" {
289                         xappnamespace = "ricxapp"
290                 }
291                 podList, _ := sbiClient.GetAllPodStatus(xappnamespace)
292
293                 for _, pod := range podList {
294                         path := fmt.Sprintf("/o-ran-sc-ric-xapp-desc-v1:ric/health/status[name='%s']", pod.Name)
295                         nbiClient.CreateNewElement(session, parent, path, "name", path)
296                         nbiClient.CreateNewElement(session, parent, path, "health", pod.Health)
297                         nbiClient.CreateNewElement(session, parent, path, "status", pod.Status)
298                 }
299                 return C.SR_ERR_OK
300         }
301
302         if mod == "o-ran-sc-ric-alarm-v1" {
303                 if alerts, _ := sbiClient.GetAlerts(); alerts != nil {
304                         for _, alert := range alerts.Payload {
305                                 id := alert.Annotations["alarm_id"]
306                                 path := fmt.Sprintf("/o-ran-sc-ric-alarm-v1:ric/alarms/alarm[alarm-id='%s']", id)
307                                 nbiClient.CreateNewElement(session, parent, path, "alarm-id", id)
308                                 nbiClient.CreateNewElement(session, parent, path, "fault-text", alert.Alert.Labels["alertname"])
309                                 nbiClient.CreateNewElement(session, parent, path, "severity", alert.Alert.Labels["severity"])
310                                 nbiClient.CreateNewElement(session, parent, path, "status", alert.Alert.Labels["status"])
311                                 nbiClient.CreateNewElement(session, parent, path, "additional-info", alert.Annotations["additional_info"])
312                         }
313                 }
314                 return C.SR_ERR_OK
315         }
316
317         gnbs, err := rnib.GetListGnbIds()
318         log.Info("Rnib.GetListGnbIds() returned elementCount=%d err:%v", len(gnbs), err)
319         if err == nil && len(gnbs) > 0 {
320                 for _, gnb := range gnbs {
321                         ranName := gnb.GetInventoryName()
322                         info, err := rnib.GetNodeb(ranName)
323                         if err != nil {
324                                 log.Error("GetNodeb() failed for ranName=%s: %v", ranName, err)
325                                 continue
326                         }
327
328                         prot := nbiClient.E2APProt2Str(int(info.E2ApplicationProtocol))
329                         connStat := nbiClient.ConnStatus2Str(int(info.ConnectionStatus))
330                         ntype := nbiClient.NodeType2Str(int(info.NodeType))
331
332                         log.Info("gNB info: %s -> %s %s %s -> %s %s", ranName, prot, connStat, ntype, gnb.GetGlobalNbId().GetPlmnId(), gnb.GetGlobalNbId().GetNbId())
333
334                         path := fmt.Sprintf("/o-ran-sc-ric-gnb-status-v1:ric/nodes/node[ran-name='%s']", ranName)
335                         nbiClient.CreateNewElement(session, parent, path, "ran-name", ranName)
336                         nbiClient.CreateNewElement(session, parent, path, "ip", info.Ip)
337                         nbiClient.CreateNewElement(session, parent, path, "port", fmt.Sprintf("%d", info.Port))
338                         nbiClient.CreateNewElement(session, parent, path, "plmn-id", gnb.GetGlobalNbId().GetPlmnId())
339                         nbiClient.CreateNewElement(session, parent, path, "nb-id", gnb.GetGlobalNbId().GetNbId())
340                         nbiClient.CreateNewElement(session, parent, path, "e2ap-protocol", prot)
341                         nbiClient.CreateNewElement(session, parent, path, "connection-status", connStat)
342                         nbiClient.CreateNewElement(session, parent, path, "node", ntype)
343                 }
344         }
345
346         //Check if any Enbs are connected to RIC
347         enbs, err2 := rnib.GetListEnbIds()
348         log.Info("Rnib.GetListEnbIds() returned elementCount=%d err:%v", len(gnbs), err)
349         if err2 == nil || len(enbs) > 0 {
350                 log.Info("Getting Enb details from list of Enbs")
351                 for _, enb := range enbs {
352                         ranName := enb.GetInventoryName()
353                         info, err := rnib.GetNodeb(ranName)
354                         if err != nil {
355                                 log.Error("GetNodeb() failed for ranName=%s: %v", ranName, err)
356                                 continue
357                         }
358
359                         prot := nbiClient.E2APProt2Str(int(info.E2ApplicationProtocol))
360                         connStat := nbiClient.ConnStatus2Str(int(info.ConnectionStatus))
361                         ntype := nbiClient.NodeType2Str(int(info.NodeType))
362
363                         log.Info("eNB info: %s -> %s %s %s -> %s %s", ranName, prot, connStat, ntype, enb.GetGlobalNbId().GetPlmnId(), enb.GetGlobalNbId().GetNbId())
364
365                         path := fmt.Sprintf("/o-ran-sc-ric-gnb-status-v1:ric/nodes/node[ran-name='%s']", ranName)
366                         nbiClient.CreateNewElement(session, parent, path, "ran-name", ranName)
367                         nbiClient.CreateNewElement(session, parent, path, "ip", info.Ip)
368                         nbiClient.CreateNewElement(session, parent, path, "port", fmt.Sprintf("%d", info.Port))
369                         nbiClient.CreateNewElement(session, parent, path, "plmn-id", enb.GetGlobalNbId().GetPlmnId())
370                         nbiClient.CreateNewElement(session, parent, path, "nb-id", enb.GetGlobalNbId().GetNbId())
371                         nbiClient.CreateNewElement(session, parent, path, "e2ap-protocol", prot)
372                         nbiClient.CreateNewElement(session, parent, path, "connection-status", connStat)
373                         nbiClient.CreateNewElement(session, parent, path, "node", ntype)
374                 }
375         }
376
377         return C.SR_ERR_OK
378 }
379
380 func (n *Nbi) CreateNewElement(session *C.sr_session_ctx_t, parent **C.char, key, name, value string) {
381         basePath := fmt.Sprintf("%s/%s", key, name)
382         log.Info("%s -> %s", basePath, value)
383
384         cPath := C.CString(basePath)
385         defer C.free(unsafe.Pointer(cPath))
386         cValue := C.CString(value)
387         defer C.free(unsafe.Pointer(cValue))
388
389         C.create_new_path(session, parent, cPath, cValue)
390 }
391
392 func (n *Nbi) ConnStatus2Str(connStatus int) string {
393         switch connStatus {
394         case 0:
395                 return "not-specified"
396         case 1:
397                 return "connected"
398         case 2:
399                 return "disconnected"
400         case 3:
401                 return "setup-failed"
402         case 4:
403                 return "connecting"
404         case 5:
405                 return "shutting-down"
406         case 6:
407                 return "shutdown"
408         }
409         return "not-specified"
410 }
411
412 func (n *Nbi) E2APProt2Str(prot int) string {
413         switch prot {
414         case 0:
415                 return "not-specified"
416         case 1:
417                 return "x2-setup-request"
418         case 2:
419                 return "endc-x2-setup-request"
420         }
421         return "not-specified"
422 }
423
424 func (n *Nbi) NodeType2Str(ntype int) string {
425         switch ntype {
426         case 0:
427                 return "not-specified"
428         case 1:
429                 return "enb"
430         case 2:
431                 return "gnb"
432         }
433         return "not-specified"
434 }
435
436 func (n *Nbi) testModuleChangeCB(module string) bool {
437         var event C.sr_event_t = C.SR_EV_CHANGE
438         reqID := C.int(100)
439         modName := C.CString(module)
440         defer C.free(unsafe.Pointer(modName))
441
442         if ret := nbiModuleChangeCB(n.session, modName, nil, event, reqID); ret != C.SR_ERR_OK {
443                 return false
444         }
445         return true
446 }
447
448 func (n *Nbi) testModuleChangeCBDone(module string) bool {
449         var event C.sr_event_t = C.SR_EV_DONE
450         reqID := C.int(100)
451         modName := C.CString(module)
452         defer C.free(unsafe.Pointer(modName))
453
454         if ret := nbiModuleChangeCB(n.session, modName, nil, event, reqID); ret != C.SR_ERR_OK {
455                 return false
456         }
457         return true
458 }
459
460 func (n *Nbi) testGnbStateCB(module string) bool {
461         modName := C.CString(module)
462         defer C.free(unsafe.Pointer(modName))
463         reqID := C.uint32_t(100)
464         parent := make([]*C.char, 1)
465
466         if ret := nbiGnbStateCB(n.session, modName, nil, nil, reqID, &parent[0]); ret != C.SR_ERR_OK {
467                 return false
468         }
469         return true
470 }
471
472 func nbGetAllXappsDefCfg(session *C.sr_session_ctx_t, parent **C.char) {
473         var xappNameList []string
474         var xappCfgList []string
475
476         //Get the default config of all deployed xapps from appgmr using rest api
477         xappNameList, xappCfgList = sbiClient.GetAllDeployedXappsConfig()
478         if xappCfgList == nil || len(xappCfgList) == 0 {
479                 log.Error("GetAllDeployedXappsConfig() Failure")
480                 return
481         }
482         log.Info("GetAllDeployedXappsConfig Success, recvd xapp config")
483
484         //Loop thru the list of recvd xapps for config
485         for i, xappCfg := range xappCfgList {
486                 path := fmt.Sprintf("/o-ran-sc-ric-xapp-desc-v1:ric/configuration/xapps/xapp[name='%s']", xappNameList[i])
487                 nbiClient.CreateNewElement(session, parent, path, "name", xappNameList[i])
488                 nbiClient.CreateNewElement(session, parent, path, "config", xappCfg)
489         }
490 }
491
492 type iRnib interface {
493         GetListGnbIds() ([]*xapp.RNIBNbIdentity, xapp.RNIBIRNibError)
494         GetListEnbIds() ([]*xapp.RNIBNbIdentity, xapp.RNIBIRNibError)
495         GetNodeb(invName string) (*xapp.RNIBNodebInfo, xapp.RNIBIRNibError)
496 }