[RIC-A-F14] Provide Platform Healthcheck for xApps via O1
[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         "strings"
29         "time"
30         "unsafe"
31
32         "gerrit.o-ran-sc.org/r/ric-plt/xapp-frame/pkg/xapp"
33         "gerrit.oran-osc.org/r/ric-plt/o1mediator/pkg/sbi"
34 )
35
36 /*
37 #cgo LDFLAGS: -lsysrepo -lyang
38
39 #include <stdio.h>
40 #include <limits.h>
41 #include <sysrepo.h>
42 #include <sysrepo/values.h>
43 #include "helper.h"
44 */
45 import "C"
46
47 var sbiClient sbi.SBIClientInterface
48 var nbiClient *Nbi
49 var log = xapp.Logger
50
51 func NewNbi(s sbi.SBIClientInterface) *Nbi {
52         sbiClient = s
53
54         nbiClient = &Nbi{
55                 schemas:     viper.GetStringSlice("nbi.schemas"),
56                 cleanupChan: make(chan bool),
57         }
58         return nbiClient
59 }
60
61 func (n *Nbi) Start() bool {
62         if ok := n.Setup(n.schemas); !ok {
63                 log.Error("NBI: SYSREPO initialization failed, bailing out!")
64                 return false
65         }
66         log.Info("NBI: SYSREPO initialization done ... processing O1 requests!")
67
68         return true
69 }
70
71 func (n *Nbi) Stop() {
72         C.sr_unsubscribe(n.subscription)
73         C.sr_session_stop(n.session)
74         C.sr_disconnect(n.connection)
75
76         log.Info("NBI: SYSREPO cleanup done gracefully!")
77 }
78
79 func (n *Nbi) Setup(schemas []string) bool {
80         rc := C.sr_connect(0, &n.connection)
81         if C.SR_ERR_OK != rc {
82                 log.Error("NBI: sr_connect failed: %s", C.GoString(C.sr_strerror(rc)))
83                 return false
84         }
85
86         rc = C.sr_session_start(n.connection, C.SR_DS_RUNNING, &n.session)
87         if C.SR_ERR_OK != rc {
88                 log.Error("NBI: sr_session_start failed: %s", C.GoString(C.sr_strerror(rc)))
89                 return false
90         }
91
92         for {
93                 if ok := n.DoSubscription(schemas); ok == true {
94                         break
95                 }
96                 time.Sleep(time.Duration(5 * time.Second))
97         }
98         return true
99 }
100
101 func (n *Nbi) DoSubscription(schemas []string) bool {
102         log.Info("Subscribing YANG modules ... %v", schemas)
103
104         for _, module := range schemas {
105                 modName := C.CString(module)
106                 defer C.free(unsafe.Pointer(modName))
107
108                 if done := n.SubscribeModule(modName); !done {
109                         return false
110                 }
111         }
112         return n.SubscribeStatusData()
113 }
114
115 func (n *Nbi) SubscribeModule(module *C.char) bool {
116         rc := C.sr_module_change_subscribe(n.session, module, nil, C.sr_module_change_cb(C.module_change_cb), nil, 0, 0, &n.subscription)
117         if C.SR_ERR_OK != rc {
118                 log.Info("NBI: sr_module_change_subscribe failed: %s", C.GoString(C.sr_strerror(rc)))
119                 return false
120         }
121         return true
122 }
123
124 func (n *Nbi) SubscribeStatusData() bool {
125         if ok := n.SubscribeStatus("o-ran-sc-ric-gnb-status-v1", "/o-ran-sc-ric-gnb-status-v1:ric/nodes"); !ok {
126                 return ok
127         }
128
129         if ok := n.SubscribeStatus("o-ran-sc-ric-xapp-desc-v1", "/o-ran-sc-ric-xapp-desc-v1:ric/health"); !ok {
130                 return ok
131         }
132         return true
133 }
134
135 func (n *Nbi) SubscribeStatus(module, xpath string) bool {
136         mod := C.CString(module)
137         path := C.CString(xpath)
138         defer C.free(unsafe.Pointer(mod))
139         defer C.free(unsafe.Pointer(path))
140
141         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)
142         if C.SR_ERR_OK != rc {
143                 log.Error("NBI: sr_oper_get_items_subscribe failed: %s", C.GoString(C.sr_strerror(rc)))
144                 return false
145         }
146         return true
147 }
148
149 //export nbiModuleChangeCB
150 func nbiModuleChangeCB(session *C.sr_session_ctx_t, module *C.char, xpath *C.char, event C.sr_event_t, reqId C.int) C.int {
151         changedModule := C.GoString(module)
152         changedXpath := C.GoString(xpath)
153
154         log.Info("NBI: change event='%d' module=%s xpath=%s reqId=%d", event, changedModule, changedXpath, reqId)
155         if C.SR_EV_CHANGE != event {
156                 log.Info("NBI: Changes finalized!")
157                 return C.SR_ERR_OK
158         }
159
160         if changedModule == "o-ran-sc-ric-xapp-desc-v1" {
161                 configJson := C.yang_data_sr2json(session, module, event, &nbiClient.oper)
162                 err := nbiClient.ManageXapps(changedModule, C.GoString(configJson), int(nbiClient.oper))
163                 if err != nil {
164                         return C.SR_ERR_OPERATION_FAILED
165                 }
166         }
167
168         if changedModule == "o-ran-sc-ric-ueec-config-v1" {
169                 configJson := C.get_data_json(session, module)
170                 err := nbiClient.ManageConfigmaps(changedModule, C.GoString(configJson), int(C.SR_OP_MODIFIED))
171                 if err != nil {
172                         return C.SR_ERR_OPERATION_FAILED
173                 }
174         }
175
176         return C.SR_ERR_OK
177 }
178
179 func (n *Nbi) ManageXapps(module, configJson string, oper int) error {
180         log.Info("ManageXapps: module=%s configJson=%s", module, configJson)
181
182         if configJson == "" {
183                 return nil
184         }
185
186         root := fmt.Sprintf("%s:ric", module)
187         jsonList, err := n.ParseJsonArray(configJson, root, "xapps", "xapp")
188         if err != nil {
189                 return err
190         }
191
192         for _, m := range jsonList {
193                 xappName := string(m.GetStringBytes("name"))
194                 namespace := string(m.GetStringBytes("namespace"))
195                 relName := string(m.GetStringBytes("release-name"))
196                 version := string(m.GetStringBytes("version"))
197
198                 desc := sbiClient.BuildXappDescriptor(xappName, namespace, relName, version)
199                 switch oper {
200                 case C.SR_OP_CREATED:
201                         return sbiClient.DeployXapp(desc)
202                 case C.SR_OP_DELETED:
203                         return sbiClient.UndeployXapp(desc)
204                 default:
205                         return errors.New(fmt.Sprintf("Operation '%d' not supported!", oper))
206                 }
207         }
208         return nil
209 }
210
211 func (n *Nbi) ManageConfigmaps(module, configJson string, oper int) error {
212         log.Info("ManageConfig: module=%s configJson=%s", module, configJson)
213
214         if configJson == "" {
215                 return nil
216         }
217
218         if oper != C.SR_OP_MODIFIED {
219                 return errors.New(fmt.Sprintf("Operation '%d' not supported!", oper))
220         }
221
222         value, err := n.ParseJson(configJson)
223         if err != nil {
224                 log.Info("ParseJson failed with error: %v", oper)
225                 return err
226         }
227
228         root := fmt.Sprintf("%s:ric", module)
229         appName := string(value.GetStringBytes(root, "config", "name"))
230         namespace := string(value.GetStringBytes(root, "config", "namespace"))
231         control := value.Get(root, "config", "control").String()
232
233         var f interface{}
234         err = json.Unmarshal([]byte(strings.ReplaceAll(control, "\\", "")), &f)
235         if err != nil {
236                 log.Info("json.Unmarshal failed: %v", err)
237                 return err
238         }
239
240         xappConfig := sbiClient.BuildXappConfig(appName, namespace, f)
241         return sbiClient.ModifyXappConfig(xappConfig)
242 }
243
244 func (n *Nbi) ParseJson(dsContent string) (*fastjson.Value, error) {
245         var p fastjson.Parser
246         v, err := p.Parse(dsContent)
247         if err != nil {
248                 log.Info("fastjson.Parser failed: %v", err)
249         }
250         return v, err
251 }
252
253 func (n *Nbi) ParseJsonArray(dsContent, model, top, elem string) ([]*fastjson.Value, error) {
254         v, err := n.ParseJson(dsContent)
255         if err != nil {
256                 return nil, err
257         }
258         return v.GetArray(model, top, elem), nil
259 }
260
261 //export nbiGnbStateCB
262 func nbiGnbStateCB(session *C.sr_session_ctx_t, module *C.char, xpath *C.char, req_xpath *C.char, reqid C.uint32_t, parent **C.char) C.int {
263         log.Info("NBI: Module state data for module='%s' path='%s' rpath='%s' requested [id=%d]", C.GoString(module), C.GoString(xpath), C.GoString(req_xpath), reqid)
264
265         if C.GoString(module) == "o-ran-sc-ric-xapp-desc-v1" {
266                 podList, _ := sbiClient.GetAllPodStatus("ricxapp")
267                 for _, pod := range podList {
268                         path := fmt.Sprintf("/o-ran-sc-ric-xapp-desc-v1:ric/health/status[name='%s']", pod.Name)
269                         nbiClient.CreateNewElement(session, parent, path, "name", path)
270                         nbiClient.CreateNewElement(session, parent, path, "health", pod.Health)
271                         nbiClient.CreateNewElement(session, parent, path, "status", pod.Status)
272                 }
273                 return C.SR_ERR_OK
274         }
275
276         gnbs, err := xapp.Rnib.GetListGnbIds()
277         if err != nil || len(gnbs) == 0 {
278                 log.Info("Rnib.GetListGnbIds() returned elementCount=%d err:%v", len(gnbs), err)
279                 return C.SR_ERR_OK
280         }
281
282         for _, gnb := range gnbs {
283                 ranName := gnb.GetInventoryName()
284                 info, err := xapp.Rnib.GetNodeb(ranName)
285                 if err != nil {
286                         log.Error("GetNodeb() failed for ranName=%s: %v", ranName, err)
287                         continue
288                 }
289
290                 prot := nbiClient.E2APProt2Str(int(info.E2ApplicationProtocol))
291                 connStat := nbiClient.ConnStatus2Str(int(info.ConnectionStatus))
292                 ntype := nbiClient.NodeType2Str(int(info.NodeType))
293
294                 log.Info("gNB info: %s -> %s %s %s -> %s %s", ranName, prot, connStat, ntype, gnb.GetGlobalNbId().GetPlmnId(), gnb.GetGlobalNbId().GetNbId())
295
296                 path := fmt.Sprintf("/o-ran-sc-ric-gnb-status-v1:ric/nodes/node[ran-name='%s']", ranName)
297                 nbiClient.CreateNewElement(session, parent, path, "ran-name", ranName)
298                 nbiClient.CreateNewElement(session, parent, path, "ip", info.Ip)
299                 nbiClient.CreateNewElement(session, parent, path, "port", fmt.Sprintf("%d", info.Port))
300                 nbiClient.CreateNewElement(session, parent, path, "plmn-id", gnb.GetGlobalNbId().GetPlmnId())
301                 nbiClient.CreateNewElement(session, parent, path, "nb-id", gnb.GetGlobalNbId().GetNbId())
302                 nbiClient.CreateNewElement(session, parent, path, "e2ap-protocol", prot)
303                 nbiClient.CreateNewElement(session, parent, path, "connection-status", connStat)
304                 nbiClient.CreateNewElement(session, parent, path, "node", ntype)
305         }
306         return C.SR_ERR_OK
307 }
308
309 func (n *Nbi) CreateNewElement(session *C.sr_session_ctx_t, parent **C.char, key, name, value string) {
310         basePath := fmt.Sprintf("%s/%s", key, name)
311         log.Info("%s -> %s", basePath, value)
312
313         cPath := C.CString(basePath)
314         defer C.free(unsafe.Pointer(cPath))
315         cValue := C.CString(value)
316         defer C.free(unsafe.Pointer(cValue))
317
318         C.create_new_path(session, parent, cPath, cValue)
319 }
320
321 func (n *Nbi) ConnStatus2Str(connStatus int) string {
322         switch connStatus {
323         case 0:
324                 return "not-specified"
325         case 1:
326                 return "connected"
327         case 2:
328                 return "disconnected"
329         case 3:
330                 return "setup-failed"
331         case 4:
332                 return "connecting"
333         case 5:
334                 return "shutting-down"
335         case 6:
336                 return "shutdown"
337         }
338         return "not-specified"
339 }
340
341 func (n *Nbi) E2APProt2Str(prot int) string {
342         switch prot {
343         case 0:
344                 return "not-specified"
345         case 1:
346                 return "x2-setup-request"
347         case 2:
348                 return "endc-x2-setup-request"
349         }
350         return "not-specified"
351 }
352
353 func (n *Nbi) NodeType2Str(ntype int) string {
354         switch ntype {
355         case 0:
356                 return "not-specified"
357         case 1:
358                 return "enb"
359         case 2:
360                 return "gnb"
361         }
362         return "not-specified"
363 }