b1304ee47b445e495986464672b63ebece4d0f83
[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         for _, module := range schemas {
104                 modName := C.CString(module)
105                 defer C.free(unsafe.Pointer(modName))
106
107                 if done := n.SubscribeModule(modName); !done {
108                         return false
109                 }
110         }
111         return n.SubscribeStatusData()
112 }
113
114 func (n *Nbi) SubscribeModule(module *C.char) bool {
115         rc := C.sr_module_change_subscribe(n.session, module, nil, C.sr_module_change_cb(C.module_change_cb), nil, 0, 0, &n.subscription)
116         if C.SR_ERR_OK != rc {
117                 log.Info("NBI: sr_module_change_subscribe failed: %s", C.GoString(C.sr_strerror(rc)))
118                 return false
119         }
120         return true
121 }
122
123 func (n *Nbi) SubscribeStatusData() bool {
124         mod := C.CString("o-ran-sc-ric-gnb-status-v1")
125         path := C.CString("/o-ran-sc-ric-gnb-status-v1:ric/nodes")
126         defer C.free(unsafe.Pointer(mod))
127         defer C.free(unsafe.Pointer(path))
128
129         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)
130         if C.SR_ERR_OK != rc {
131                 log.Error("NBI: sr_oper_get_items_subscribe failed: %s", C.GoString(C.sr_strerror(rc)))
132                 return false
133         }
134         return true
135 }
136
137 //export nbiModuleChangeCB
138 func nbiModuleChangeCB(session *C.sr_session_ctx_t, module *C.char, xpath *C.char, event C.sr_event_t, reqId C.int) C.int {
139         changedModule := C.GoString(module)
140         changedXpath := C.GoString(xpath)
141
142         log.Info("NBI: Module change callback - event='%d' module=%s xpath=%s reqId=%d", event, changedModule, changedXpath, reqId)
143
144         if C.SR_EV_CHANGE == event {
145                 configJson := C.yang_data_sr2json(session, module, event, &nbiClient.oper)
146                 err := nbiClient.ManageXapps(changedModule, C.GoString(configJson), int(nbiClient.oper))
147                 if err != nil {
148                         return C.SR_ERR_OPERATION_FAILED
149                 }
150         }
151
152         if C.SR_EV_DONE == event {
153                 configJson := C.get_data_json(session, module)
154                 err := nbiClient.ManageConfigmaps(changedModule, C.GoString(configJson), int(nbiClient.oper))
155                 if err != nil {
156                         return C.SR_ERR_OPERATION_FAILED
157                 }
158         }
159
160         return C.SR_ERR_OK
161 }
162
163 func (n *Nbi) ManageXapps(module, configJson string, oper int) error {
164         log.Info("ManageXapps: module=%s configJson=%s", module, configJson)
165
166         if configJson == "" || module != "o-ran-sc-ric-xapp-desc-v1" {
167                 return nil
168         }
169
170         root := fmt.Sprintf("%s:ric", module)
171         jsonList, err := n.ParseJsonArray(configJson, root, "xapps", "xapp")
172         if err != nil {
173                 return err
174         }
175
176         for _, m := range jsonList {
177                 xappName := string(m.GetStringBytes("name"))
178                 namespace := string(m.GetStringBytes("namespace"))
179                 relName := string(m.GetStringBytes("release-name"))
180                 version := string(m.GetStringBytes("version"))
181
182                 desc := sbiClient.BuildXappDescriptor(xappName, namespace, relName, version)
183                 switch oper {
184                 case C.SR_OP_CREATED:
185                         return sbiClient.DeployXapp(desc)
186                 case C.SR_OP_DELETED:
187                         return sbiClient.UndeployXapp(desc)
188                 default:
189                         return errors.New(fmt.Sprintf("Operation '%d' not supported!", oper))
190                 }
191         }
192         return nil
193 }
194
195 func (n *Nbi) ManageConfigmaps(module, configJson string, oper int) error {
196         log.Info("ManageConfig: module=%s configJson=%s", module, configJson)
197
198         if configJson == "" || module != "o-ran-sc-ric-ueec-config-v1" {
199                 return nil
200         }
201
202         if oper != C.SR_OP_MODIFIED {
203                 return errors.New(fmt.Sprintf("Operation '%d' not supported!", oper))
204         }
205
206         value, err := n.ParseJson(configJson)
207         if err != nil {
208                 return err
209         }
210
211         root := fmt.Sprintf("%s:ric", module)
212         appName := string(value.GetStringBytes(root, "config", "name"))
213         namespace := string(value.GetStringBytes(root, "config", "namespace"))
214         control := value.Get(root, "config", "control").String()
215
216         var f interface{}
217         err = json.Unmarshal([]byte(strings.ReplaceAll(control, "\\", "")), &f)
218         if err != nil {
219                 log.Info("json.Unmarshal failed: %v", err)
220                 return err
221         }
222
223         xappConfig := sbiClient.BuildXappConfig(appName, namespace, f)
224         return sbiClient.ModifyXappConfig(xappConfig)
225 }
226
227 func (n *Nbi) ParseJson(dsContent string) (*fastjson.Value, error) {
228         var p fastjson.Parser
229         v, err := p.Parse(dsContent)
230         if err != nil {
231                 log.Info("fastjson.Parser failed: %v", err)
232         }
233         return v, err
234 }
235
236 func (n *Nbi) ParseJsonArray(dsContent, model, top, elem string) ([]*fastjson.Value, error) {
237         v, err := n.ParseJson(dsContent)
238         if err != nil {
239                 return nil, err
240         }
241         return v.GetArray(model, top, elem), nil
242 }
243
244 //export nbiGnbStateCB
245 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 {
246         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)
247
248         gnbs, err := xapp.Rnib.GetListGnbIds()
249         if err != nil || len(gnbs) == 0 {
250                 log.Info("Rnib.GetListGnbIds() returned elementCount=%d err:%v", len(gnbs), err)
251                 return C.SR_ERR_OK
252         }
253
254         for _, gnb := range gnbs {
255                 ranName := gnb.GetInventoryName()
256                 info, err := xapp.Rnib.GetNodeb(ranName)
257                 if err != nil {
258                         log.Error("GetNodeb() failed for ranName=%s: %v", ranName, err)
259                         continue
260                 }
261
262                 prot := nbiClient.E2APProt2Str(int(info.E2ApplicationProtocol))
263                 connStat := nbiClient.ConnStatus2Str(int(info.ConnectionStatus))
264                 ntype := nbiClient.NodeType2Str(int(info.NodeType))
265
266                 log.Info("gNB info: %s -> %s %s %s -> %s %s", ranName, prot, connStat, ntype, gnb.GetGlobalNbId().GetPlmnId(), gnb.GetGlobalNbId().GetNbId())
267
268                 nbiClient.CreateNewElement(session, parent, ranName, "ran-name", ranName)
269                 nbiClient.CreateNewElement(session, parent, ranName, "ip", info.Ip)
270                 nbiClient.CreateNewElement(session, parent, ranName, "port", fmt.Sprintf("%d", info.Port))
271                 nbiClient.CreateNewElement(session, parent, ranName, "plmn-id", gnb.GetGlobalNbId().GetPlmnId())
272                 nbiClient.CreateNewElement(session, parent, ranName, "nb-id", gnb.GetGlobalNbId().GetNbId())
273                 nbiClient.CreateNewElement(session, parent, ranName, "e2ap-protocol", prot)
274                 nbiClient.CreateNewElement(session, parent, ranName, "connection-status", connStat)
275                 nbiClient.CreateNewElement(session, parent, ranName, "node", ntype)
276         }
277         return C.SR_ERR_OK
278 }
279
280 func (n *Nbi) CreateNewElement(session *C.sr_session_ctx_t, parent **C.char, key, name, value string) {
281         basePath := fmt.Sprintf("/o-ran-sc-ric-gnb-status-v1:ric/nodes/node[ran-name='%s']/%s", key, name)
282         log.Info("%s -> %s", basePath, value)
283
284         cPath := C.CString(basePath)
285         defer C.free(unsafe.Pointer(cPath))
286         cValue := C.CString(value)
287         defer C.free(unsafe.Pointer(cValue))
288
289         C.create_new_path(session, parent, cPath, cValue)
290 }
291
292 func (n *Nbi) ConnStatus2Str(connStatus int) string {
293         switch connStatus {
294         case 0:
295                 return "not-specified"
296         case 1:
297                 return "connected"
298         case 2:
299                 return "disconnected"
300         case 3:
301                 return "setup-failed"
302         case 4:
303                 return "connecting"
304         case 5:
305                 return "shutting-down"
306         case 6:
307                 return "shutdown"
308         }
309         return "not-specified"
310 }
311
312 func (n *Nbi) E2APProt2Str(prot int) string {
313         switch prot {
314         case 0:
315                 return "not-specified"
316         case 1:
317                 return "x2-setup-request"
318         case 2:
319                 return "endc-x2-setup-request"
320         }
321         return "not-specified"
322 }
323
324 func (n *Nbi) NodeType2Str(ntype int) string {
325         switch ntype {
326         case 0:
327                 return "not-specified"
328         case 1:
329                 return "enb"
330         case 2:
331                 return "gnb"
332         }
333         return "not-specified"
334 }