blob: 2297b66c725d339599ee5465825906066deabe83 [file] [log] [blame]
Elia Battistonac8d23f2022-03-14 17:54:56 +01001/*
2* Copyright 2022-present Open Networking Foundation
3
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7
8* http://www.apache.org/licenses/LICENSE-2.0
9
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15 */
16
17package sysrepo
18
Elia Battistone1cecb22022-03-21 10:05:25 +010019//#cgo LDFLAGS: -lsysrepo -lyang -Wl,--allow-multiple-definition
Elia Battistonac8d23f2022-03-14 17:54:56 +010020//#include "plugin.c"
21import "C"
22import (
23 "context"
24 "fmt"
Elia Battiston589addb2022-04-04 16:40:01 +020025 "io/ioutil"
26 "os"
Elia Battistone1cecb22022-03-21 10:05:25 +010027 "unsafe"
Elia Battistonac8d23f2022-03-14 17:54:56 +010028
29 "github.com/opencord/voltha-lib-go/v7/pkg/log"
Elia Battistone1cecb22022-03-21 10:05:25 +010030 "github.com/opencord/voltha-northbound-bbf-adapter/internal/core"
Elia Battistonac8d23f2022-03-14 17:54:56 +010031)
32
33type SysrepoPlugin struct {
Elia Battiston4750d3c2022-07-14 13:24:56 +000034 connection *C.sr_conn_ctx_t
35 operationalSession *C.sr_session_ctx_t
36 runningSession *C.sr_session_ctx_t
37 subscription *C.sr_subscription_ctx_t
38 schemaMountData *C.lyd_node
Elia Battistone1cecb22022-03-21 10:05:25 +010039}
40
Elia Battistonac8d23f2022-03-14 17:54:56 +010041//createPluginState populates a SysrepoPlugin struct by establishing
42//a connection and a session
Elia Battiston4750d3c2022-07-14 13:24:56 +000043func (p *SysrepoPlugin) createSessions(ctx context.Context) error {
Elia Battistonac8d23f2022-03-14 17:54:56 +010044 var errCode C.int
45
46 //Populates connection
47 errCode = C.sr_connect(C.SR_CONN_DEFAULT, &p.connection)
48 if errCode != C.SR_ERR_OK {
49 err := fmt.Errorf("sysrepo-connect-error")
Elia Battiston589addb2022-04-04 16:40:01 +020050 logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
Elia Battistonac8d23f2022-03-14 17:54:56 +010051 return err
52 }
53
Elia Battiston4750d3c2022-07-14 13:24:56 +000054 //Populates sessions
55 //The session on the operation datastore will be used for most operations
56 //The session on the running datastore will be used for the subscription to edits
57 //since the operational datastore can't be edited by the client
58 errCode = C.sr_session_start(p.connection, C.SR_DS_OPERATIONAL, &p.operationalSession)
Elia Battistonac8d23f2022-03-14 17:54:56 +010059 if errCode != C.SR_ERR_OK {
Elia Battiston4750d3c2022-07-14 13:24:56 +000060 err := fmt.Errorf("sysrepo-operational-session-error")
61 logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
62
63 _ = p.Stop(ctx)
64
65 return err
66 }
67
68 errCode = C.sr_session_start(p.connection, C.SR_DS_RUNNING, &p.runningSession)
69 if errCode != C.SR_ERR_OK {
70 err := fmt.Errorf("sysrepo-running-session-error")
Elia Battiston589addb2022-04-04 16:40:01 +020071 logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
Elia Battistonac8d23f2022-03-14 17:54:56 +010072
73 _ = p.Stop(ctx)
74
75 return err
76 }
77
78 return nil
79}
80
Elia Battiston589addb2022-04-04 16:40:01 +020081func StartNewPlugin(ctx context.Context, schemaMountFilePath string) (*SysrepoPlugin, error) {
Elia Battistonac8d23f2022-03-14 17:54:56 +010082 plugin := &SysrepoPlugin{}
83
Elia Battiston4750d3c2022-07-14 13:24:56 +000084 //Set sysrepo and libyang log level
85 if logger.GetLogLevel() == log.DebugLevel {
86 C.sr_log_stderr(C.SR_LL_INF)
87 C.ly_log_level(C.LY_LLVRB)
88 } else {
89 C.sr_log_stderr(C.SR_LL_ERR)
90 C.ly_log_level(C.LY_LLERR)
91 }
92
Elia Battistonac8d23f2022-03-14 17:54:56 +010093 //Open a session to sysrepo
Elia Battiston4750d3c2022-07-14 13:24:56 +000094 err := plugin.createSessions(ctx)
Elia Battistonac8d23f2022-03-14 17:54:56 +010095 if err != nil {
96 return nil, err
97 }
98
Elia Battiston589addb2022-04-04 16:40:01 +020099 //Read the schema-mount file
100 if _, err := os.Stat(schemaMountFilePath); err != nil {
101 //The file cannot be found
102 return nil, fmt.Errorf("plugin-startup-schema-mount-file-not-found: %v", err)
103 }
104
105 smBuffer, err := ioutil.ReadFile(schemaMountFilePath)
106 if err != nil {
107 return nil, fmt.Errorf("plugin-startup-cannot-read-schema-mount-file: %v", err)
108 }
109
110 smString := C.CString(string(smBuffer))
111 defer freeCString(smString)
112
113 ly_ctx := C.sr_acquire_context(plugin.connection)
114 defer C.sr_release_context(plugin.connection)
115 if ly_ctx == nil {
116 return nil, fmt.Errorf("plugin-startup-null-libyang-context")
117 }
118
119 //Parse the schema-mount file into libyang nodes, and save them into the plugin data
120 lyErrCode := C.lyd_parse_data_mem(ly_ctx, smString, C.LYD_XML, C.LYD_PARSE_STRICT, C.LYD_VALIDATE_PRESENT, &plugin.schemaMountData)
121 if lyErrCode != C.LY_SUCCESS {
122 return nil, fmt.Errorf("plugin-startup-cannot-parse-schema-mount: %v", lyErrorMsg(ly_ctx))
123 }
124
125 //Bind the callback needed to support schema-mount
126 C.sr_set_ext_data_cb(plugin.connection, C.function(C.mountpoint_ext_data_clb), unsafe.Pointer(plugin.schemaMountData))
Elia Battistonac8d23f2022-03-14 17:54:56 +0100127
Elia Battistonaa7a0482022-08-17 12:24:02 +0000128 //Reconcile the content of the running datastore with the services in ONOS
129 //i.e. create yang nodes for already provisioned services
130 //so that they can be removed through NETCONF with an edit-config
131 if err := plugin.reconcileServices(ctx); err != nil {
132 return nil, fmt.Errorf("plugin-startup-cannot-reconcile-services: %v", err)
133 }
134
Elia Battistonac8d23f2022-03-14 17:54:56 +0100135 //Set callbacks for events
136
137 //Subscribe with a callback to the request of data on a certain path
Elia Battiston4750d3c2022-07-14 13:24:56 +0000138 devicesModule := C.CString(core.DeviceAggregationModule)
139 devicesPath := C.CString(core.DevicesPath + "/*")
140 defer freeCString(devicesModule)
141 defer freeCString(devicesPath)
Elia Battistone1cecb22022-03-21 10:05:25 +0100142
Elia Battistona1333642022-07-27 12:17:24 +0000143 servicesModule := C.CString(core.ServiceProfileModule)
144 servicesPath := C.CString(core.ServiceProfilesPath + "/*")
145 defer freeCString(servicesModule)
146 defer freeCString(servicesPath)
147
148 vlansModule := C.CString(core.VlansModule)
149 vlansPath := C.CString(core.VlansPath + "/*")
150 defer freeCString(vlansModule)
151 defer freeCString(vlansPath)
152
153 bwProfilesModule := C.CString(core.BandwidthProfileModule)
154 bwProfilesPath := C.CString(core.BandwidthProfilesPath + "/*")
155 defer freeCString(bwProfilesModule)
156 defer freeCString(bwProfilesPath)
157
158 //Get devices
Elia Battistonb244bb52022-03-24 15:47:16 +0100159 errCode := C.sr_oper_get_subscribe(
Elia Battiston4750d3c2022-07-14 13:24:56 +0000160 plugin.operationalSession,
161 devicesModule,
162 devicesPath,
Elia Battistone1cecb22022-03-21 10:05:25 +0100163 C.function(C.get_devices_cb_wrapper),
Elia Battistonac8d23f2022-03-14 17:54:56 +0100164 C.NULL,
Elia Battistonb244bb52022-03-24 15:47:16 +0100165 C.SR_SUBSCR_DEFAULT,
Elia Battistonac8d23f2022-03-14 17:54:56 +0100166 &plugin.subscription,
167 )
168 if errCode != C.SR_ERR_OK {
Elia Battistona1333642022-07-27 12:17:24 +0000169 err := fmt.Errorf("sysrepo-failed-subscription-to-get-devices")
170 logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
171 return nil, err
172 }
173
174 //Get services
175 errCode = C.sr_oper_get_subscribe(
176 plugin.operationalSession,
177 servicesModule,
178 servicesPath,
179 C.function(C.get_services_cb_wrapper),
180 C.NULL,
181 C.SR_SUBSCR_DEFAULT,
182 &plugin.subscription,
183 )
184 if errCode != C.SR_ERR_OK {
185 err := fmt.Errorf("sysrepo-failed-subscription-to-get-services")
186 logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
187 return nil, err
188 }
189
190 //Get vlans
191 errCode = C.sr_oper_get_subscribe(
192 plugin.operationalSession,
193 vlansModule,
194 vlansPath,
195 C.function(C.get_vlans_cb_wrapper),
196 C.NULL,
197 C.SR_SUBSCR_DEFAULT,
198 &plugin.subscription,
199 )
200 if errCode != C.SR_ERR_OK {
201 err := fmt.Errorf("sysrepo-failed-subscription-to-get-services")
202 logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
203 return nil, err
204 }
205
206 //Get bandwidth profiles
207 errCode = C.sr_oper_get_subscribe(
208 plugin.operationalSession,
209 bwProfilesModule,
210 bwProfilesPath,
211 C.function(C.get_bandwidth_profiles_cb_wrapper),
212 C.NULL,
213 C.SR_SUBSCR_DEFAULT,
214 &plugin.subscription,
215 )
216 if errCode != C.SR_ERR_OK {
217 err := fmt.Errorf("sysrepo-failed-subscription-to-get-services")
218 logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
219 return nil, err
220 }
221
222 //Subscribe with a callback to changes of configuration in the services modules
223 //Changes to services
224 errCode = C.sr_module_change_subscribe(
225 plugin.runningSession,
226 servicesModule,
227 servicesPath,
228 C.function(C.edit_service_profiles_cb_wrapper),
229 unsafe.Pointer(plugin.runningSession), //Pass session for running datastore to get current data
230 0,
231 C.SR_SUBSCR_DEFAULT,
232 &plugin.subscription,
233 )
234 if errCode != C.SR_ERR_OK {
235 err := fmt.Errorf("sysrepo-failed-subscription-to-change-services")
236 logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
237 return nil, err
238 }
239
240 //Changes to VLANs
241 errCode = C.sr_module_change_subscribe(
242 plugin.runningSession,
243 vlansModule,
244 vlansPath,
245 C.function(C.edit_vlans_cb_wrapper),
246 C.NULL,
247 0,
248 C.SR_SUBSCR_DEFAULT,
249 &plugin.subscription,
250 )
251 if errCode != C.SR_ERR_OK {
252 err := fmt.Errorf("sysrepo-failed-subscription-to-change-vlans")
Elia Battiston589addb2022-04-04 16:40:01 +0200253 logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
Elia Battistonac8d23f2022-03-14 17:54:56 +0100254 return nil, err
255 }
256
257 logger.Debug(ctx, "sysrepo-plugin-started")
258
259 return plugin, nil
260}
261
262func (p *SysrepoPlugin) Stop(ctx context.Context) error {
263 var errCode C.int
264
Elia Battiston589addb2022-04-04 16:40:01 +0200265 //Free the libyang nodes for external schema-mount data
266 C.lyd_free_all(p.schemaMountData)
267
Elia Battistonac8d23f2022-03-14 17:54:56 +0100268 //Frees subscription
269 if p.subscription != nil {
270 errCode = C.sr_unsubscribe(p.subscription)
271 if errCode != C.SR_ERR_OK {
272 err := fmt.Errorf("failed-to-close-sysrepo-subscription")
Elia Battiston589addb2022-04-04 16:40:01 +0200273 logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
Elia Battistonac8d23f2022-03-14 17:54:56 +0100274 return err
275 }
276 p.subscription = nil
277 }
278
Elia Battiston4750d3c2022-07-14 13:24:56 +0000279 //Frees sessions
280 if p.operationalSession != nil {
281 errCode = C.sr_session_stop(p.operationalSession)
Elia Battistonac8d23f2022-03-14 17:54:56 +0100282 if errCode != C.SR_ERR_OK {
Elia Battiston4750d3c2022-07-14 13:24:56 +0000283 err := fmt.Errorf("failed-to-close-operational-session")
Elia Battiston589addb2022-04-04 16:40:01 +0200284 logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
Elia Battistonac8d23f2022-03-14 17:54:56 +0100285 return err
286 }
Elia Battiston4750d3c2022-07-14 13:24:56 +0000287 p.operationalSession = nil
288 }
289
290 if p.runningSession != nil {
291 errCode = C.sr_session_stop(p.runningSession)
292 if errCode != C.SR_ERR_OK {
293 err := fmt.Errorf("failed-to-close-running-session")
294 logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
295 return err
296 }
297 p.runningSession = nil
Elia Battistonac8d23f2022-03-14 17:54:56 +0100298 }
299
300 //Frees connection
301 if p.connection != nil {
302 errCode = C.sr_disconnect(p.connection)
303 if errCode != C.SR_ERR_OK {
304 err := fmt.Errorf("failed-to-close-sysrepo-connection")
Elia Battiston589addb2022-04-04 16:40:01 +0200305 logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
Elia Battistonac8d23f2022-03-14 17:54:56 +0100306 return err
307 }
308 p.connection = nil
309 }
310
311 logger.Debug(ctx, "sysrepo-plugin-stopped")
312
313 return nil
314}
Elia Battistonaa7a0482022-08-17 12:24:02 +0000315
316//reconcileServices retrieves the currently active services from ONOS
317//and creates YANG nodes in the running datastore to allow their management
318//through NETCONF
319func (p *SysrepoPlugin) reconcileServices(ctx context.Context) error {
320 if core.AdapterInstance == nil {
321 return fmt.Errorf("nil-adapter-instance")
322 }
323
324 //Get and create VLANs first, otherwise validation for services will fail
325 vlansItems, err := core.AdapterInstance.GetVlans(ctx)
326 if err != nil {
327 return fmt.Errorf("cannot-get-programmed-vlans: %v", err)
328 }
329
330 if len(vlansItems) > 0 {
331 vlansTree, err := createYangTree(ctx, p.runningSession, vlansItems)
332 if err != nil {
333 return fmt.Errorf("cannot-create-vlans-tree: %v", err)
334 }
335 defer C.lyd_free_all(vlansTree)
336
337 err = editDatastore(ctx, p.runningSession, vlansTree)
338 if err != nil {
339 return fmt.Errorf("cannot-add-vlans-to-datastore: %v", err)
340 }
341 }
342
343 //Get and create nodes for services
344 servicesItems, err := core.AdapterInstance.GetServices(ctx)
345 if err != nil {
346 return fmt.Errorf("cannot-get-programmed-services: %v", err)
347 }
348
349 if len(servicesItems) > 0 {
350 servicesTree, err := createYangTree(ctx, p.runningSession, servicesItems)
351 if err != nil {
352 return fmt.Errorf("cannot-create-services-tree: %v", err)
353 }
354 defer C.lyd_free_all(servicesTree)
355
356 err = editDatastore(ctx, p.runningSession, servicesTree)
357 if err != nil {
358 return fmt.Errorf("cannot-add-services-to-datastore: %v", err)
359 }
360 }
361
362 logger.Info(ctx, "reconciled-active-services")
363
364 return nil
365}