blob: 94a05bbe9baea40eb6a56d9f83fcd41d6d22f2d7 [file] [log] [blame]
Radek Krejcicbbb1972017-09-21 13:25:19 +02001"""
2NETCONF connections
3File: connections.py
4Author: Radek Krejci <rkrejci@cesnet.cz>
5"""
6
Radek Krejci67c922d2017-09-21 13:56:41 +02007import json
8import os
9
Radek Krejcicbbb1972017-09-21 13:25:19 +020010from liberouterapi import auth
11from flask import request
Radek Krejci0f793fe2018-02-14 08:33:45 +010012import yang
Radek Krejcicbbb1972017-09-21 13:25:19 +020013import netconf2 as nc
Radek Krejcicbbb1972017-09-21 13:25:19 +020014
15from .inventory import INVENTORY
16from .devices import devices_get
17from .error import NetopeerException
Radek Krejcia1339602017-11-02 13:52:38 +010018from .data import *
Radek Krejcicbbb1972017-09-21 13:25:19 +020019
20sessions = {}
21
Radek Krejcie20e4d82017-11-08 14:18:05 +010022def hostkey_check(hostname, state, keytype, hexa, priv):
23 # TODO real check
24 return True
25
Radek Krejcicbbb1972017-09-21 13:25:19 +020026@auth.required()
27def connect():
28 session = auth.lookup(request.headers.get('Authorization', None))
29 user = session['user']
30 path = os.path.join(INVENTORY, user.username)
31
Radek Krejcia6c8b412017-10-17 16:59:38 +020032 data = request.get_json()
33 if 'id' in data:
34 # stored device
35 device = devices_get(data['id'], user.username)
36 elif 'device' in data:
37 # one-time connect, the device is specified in request
38 device = data['device']
39 else:
40 raise NetopeerException('Invalid connection request.')
41
Radek Krejcicbbb1972017-09-21 13:25:19 +020042 if not device:
43 raise NetopeerException('Unknown device to connect to request.')
44
45 nc.setSearchpath(path)
46
47 ssh = nc.SSH(device['username'], password=device['password'])
Radek Krejcie20e4d82017-11-08 14:18:05 +010048 ssh.setAuthHostkeyCheckClb(hostkey_check)
Radek Krejcicbbb1972017-09-21 13:25:19 +020049 try:
50 session = nc.Session(device['hostname'], device['port'], ssh)
51 except Exception as e:
52 return(json.dumps({'success': False, 'error-msg': str(e)}))
53
54 if not user.username in sessions:
55 sessions[user.username] = {}
56
57 # use key (as hostname:port:session-id) to store the created NETCONF session
58 key = session.host + ":" + str(session.port) + ":" + session.id
Radek Krejci4d3896c2018-01-08 17:10:43 +010059 sessions[user.username][key] = {}
60 sessions[user.username][key]['session'] = session
Radek Krejcicbbb1972017-09-21 13:25:19 +020061
62 return(json.dumps({'success': True, 'session-key': key}))
63
64
65@auth.required()
66def session_get_capabilities():
67 session = auth.lookup(request.headers.get('Authorization', None))
68 user = session['user']
69 req = request.args.to_dict()
70
71 if not 'key' in req:
72 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
73
74 if not user.username in sessions:
75 sessions[user.username] = {}
76
77 key = req['key']
78 if not key in sessions[user.username]:
79 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
80
81 cpblts = []
Radek Krejci4d3896c2018-01-08 17:10:43 +010082 for c in sessions[user.username][key]['session'].capabilities:
Radek Krejcicbbb1972017-09-21 13:25:19 +020083 cpblts.append(c)
84
85 return(json.dumps({'success': True, 'capabilities': cpblts}))
86
87@auth.required()
Radek Krejci2e578562017-10-17 11:11:13 +020088def session_get():
89 session = auth.lookup(request.headers.get('Authorization', None))
90 user = session['user']
91 req = request.args.to_dict()
92
93 if not 'key' in req:
94 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
Radek Krejcia1339602017-11-02 13:52:38 +010095 if not 'recursive' in req:
96 return(json.dumps({'success': False, 'error-msg': 'Missing recursive flag.'}))
Radek Krejci2e578562017-10-17 11:11:13 +020097
98 if not user.username in sessions:
99 sessions[user.username] = {}
100
101 key = req['key']
102 if not key in sessions[user.username]:
103 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
104
Radek Krejci2e578562017-10-17 11:11:13 +0200105 try:
Radek Krejci4d3896c2018-01-08 17:10:43 +0100106 sessions[user.username][key]['data'] = sessions[user.username][key]['session'].rpcGet()
Radek Krejci2e578562017-10-17 11:11:13 +0200107 except nc.ReplyError as e:
108 reply = {'success': False, 'error': []}
109 for err in e.args[0]:
110 reply['error'].append(json.loads(str(err)))
111 return(json.dumps(reply))
112
Radek Krejcia1339602017-11-02 13:52:38 +0100113 if not 'path' in req:
Radek Krejci4d3896c2018-01-08 17:10:43 +0100114 return(dataInfoRoots(sessions[user.username][key]['data'], True if req['recursive'] == 'true' else False))
Radek Krejcia1339602017-11-02 13:52:38 +0100115 else:
Radek Krejci4d3896c2018-01-08 17:10:43 +0100116 return(dataInfoSubtree(sessions[user.username][key]['data'], req['path'], True if req['recursive'] == 'true' else False))
117
118
Radek Krejci6e772b22018-01-25 13:28:57 +0100119def _checkvalue(session, req, schema):
120 user = session['user'];
Radek Krejci4d3896c2018-01-08 17:10:43 +0100121
122 if not 'key' in req:
123 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
124 if not 'path' in req:
125 return(json.dumps({'success': False, 'error-msg': 'Missing path to validate value.'}))
126 if not 'value' in req:
127 return(json.dumps({'success': False, 'error-msg': 'Missing value to validate.'}))
128
Radek Krejci6e772b22018-01-25 13:28:57 +0100129 key = req['key']
130 if not key in sessions[user.username]:
131 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
132
Radek Krejcicf719cb2018-02-15 16:47:32 +0100133 ctx = sessions[user.username][key]['session'].context;
Radek Krejci6e772b22018-01-25 13:28:57 +0100134 if schema:
Radek Krejcicf719cb2018-02-15 16:47:32 +0100135 search = ctx.find_path(req['path'])
Radek Krejci6e772b22018-01-25 13:28:57 +0100136 else:
137 search = sessions[user.username][key]['data'].find_path(req['path'])
138
139 if search.number() != 1:
140 return(json.dumps({'success': False, 'error-msg': 'Invalid data path.'}))
141
142 if schema:
143 node = search.schema()[0]
144 else:
145 node = search.data()[0]
146
147 if node.validate_value(req['value']):
Radek Krejcicf719cb2018-02-15 16:47:32 +0100148 errors = yang.get_ly_errors(ctx)
149 if errors.size():
150 return(json.dumps({'success': False, 'error-msg': errors[errors.size() - 1].errmsg()}))
151 else:
152 return(json.dumps({'success': False, 'error-msg': 'unknown error'}))
Radek Krejci6e772b22018-01-25 13:28:57 +0100153
154 return(json.dumps({'success': True}))
155
156@auth.required()
157def data_checkvalue():
158 session = auth.lookup(request.headers.get('Authorization', None))
159 req = request.args.to_dict()
160
161 return _checkvalue(session, req, False)
162
163
164@auth.required()
165def schema_checkvalue():
166 session = auth.lookup(request.headers.get('Authorization', None))
167 req = request.args.to_dict()
168
169 return _checkvalue(session, req, True)
170
171
172@auth.required()
Radek Krejcid0ce4cf2018-02-09 14:44:34 +0100173def schema_values():
174 session = auth.lookup(request.headers.get('Authorization', None))
175 user = session['user']
176 req = request.args.to_dict()
177
178 if not 'key' in req:
179 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
180 if not 'path' in req:
181 return(json.dumps({'success': False, 'error-msg': 'Missing path to validate value.'}))
182
183 key = req['key']
184 if not key in sessions[user.username]:
185 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
186
187 search = sessions[user.username][key]['session'].context.find_path(req['path'])
188 if search.number() != 1:
189 return(json.dumps({'success': False, 'error-msg': 'Invalid data path.'}))
190 schema = search.schema()[0]
191
Radek Krejci0f793fe2018-02-14 08:33:45 +0100192 if schema.nodetype() != yang.LYS_LEAF and schema.nodetype != yang.LYS_LEAFLIST:
Radek Krejcid0ce4cf2018-02-09 14:44:34 +0100193 result = None
194 else:
195 result = typeValues(schema.subtype().type(), [])
196 return(json.dumps({'success': True, 'data': result}))
197
198
199@auth.required()
Radek Krejci6e772b22018-01-25 13:28:57 +0100200def schema_info():
201 session = auth.lookup(request.headers.get('Authorization', None))
202 user = session['user']
203 req = request.args.to_dict()
204
205 if not 'key' in req:
206 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
207 if not 'path' in req:
208 return(json.dumps({'success': False, 'error-msg': 'Missing path to validate value.'}))
Radek Krejci4d3896c2018-01-08 17:10:43 +0100209
210 key = req['key']
211 if not key in sessions[user.username]:
212 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
213
Radek Krejci6e772b22018-01-25 13:28:57 +0100214 if req['path'] == '/':
Radek Krejcif71867f2018-01-30 13:28:28 +0100215 node = None
Radek Krejci6e772b22018-01-25 13:28:57 +0100216 else:
217 search = sessions[user.username][key]['session'].context.find_path(req['path'])
218 if search.number() != 1:
219 return(json.dumps({'success': False, 'error-msg': 'Invalid data path.'}))
220 node = search.schema()[0]
Radek Krejci4d3896c2018-01-08 17:10:43 +0100221
Radek Krejci6e772b22018-01-25 13:28:57 +0100222 result = [];
223 if 'relative' in req:
224 if req['relative'] == 'children':
Radek Krejcif71867f2018-01-30 13:28:28 +0100225 if node:
226 instantiables = node.child_instantiables(0)
227 else:
228 # top level
229 instantiables = sessions[user.username][key]['session'].context.data_instantiables(0)
Radek Krejci6e772b22018-01-25 13:28:57 +0100230 elif req['relative'] == 'siblings':
231 if node.parent():
Radek Krejcif71867f2018-01-30 13:28:28 +0100232 instantiables = node.parent().child_instantiables(0)
233 else:
234 # top level
235 instantiables = sessions[user.username][key]['session'].context.data_instantiables(0)
Radek Krejci6e772b22018-01-25 13:28:57 +0100236 else:
237 return(json.dumps({'success': False, 'error-msg': 'Invalid relative parameter.'}))
Radek Krejcif71867f2018-01-30 13:28:28 +0100238
239 for child in instantiables:
Radek Krejci0f793fe2018-02-14 08:33:45 +0100240 if child.flags() & yang.LYS_CONFIG_R:
Radek Krejcif71867f2018-01-30 13:28:28 +0100241 # ignore status nodes
242 continue
Radek Krejci0f793fe2018-02-14 08:33:45 +0100243 if child.nodetype() & (yang.LYS_RPC | yang.LYS_NOTIF | yang.LYS_ACTION):
Radek Krejcif71867f2018-01-30 13:28:28 +0100244 # ignore RPCs, Notifications and Actions
245 continue
246 result.append(schemaInfoNode(child))
Radek Krejci6e772b22018-01-25 13:28:57 +0100247 else:
248 result.append(schemaInfoNode(node))
Radek Krejci4d3896c2018-01-08 17:10:43 +0100249
Radek Krejci6e772b22018-01-25 13:28:57 +0100250 return(json.dumps({'success': True, 'data': result}))
Radek Krejci2e578562017-10-17 11:11:13 +0200251
252
Radek Krejcif041d652018-02-06 10:21:25 +0100253def _create_child(ctx, parent, child_def):
254 module = ctx.get_module(child_def['info']['module'])
255 # print('child: ' + json.dumps(child_def))
Radek Krejci7a244f02018-02-21 09:46:18 +0100256 # print('parent: ' + parent.schema().name())
257 # print('module: ' + module.name())
258 # print('name: ' + child_def['info']['name'])
259 if child_def['info']['type'] == 4 :
260 # print('value: ' + str(child_def['value']))
Radek Krejci0f793fe2018-02-14 08:33:45 +0100261 yang.Data_Node(parent, module, child_def['info']['name'], child_def['value'])
Radek Krejci7a244f02018-02-21 09:46:18 +0100262 elif child_def['info']['type'] == 8:
263 # print('value: ' + child_def['value'][0])
264 yang.Data_Node(parent, module, child_def['info']['name'], child_def['value'][0])
Radek Krejcif041d652018-02-06 10:21:25 +0100265 else:
Radek Krejci0f793fe2018-02-14 08:33:45 +0100266 child = yang.Data_Node(parent, module, child_def['info']['name'])
Radek Krejcif041d652018-02-06 10:21:25 +0100267 if 'children' in child_def:
268 for grandchild in child_def['children']:
269 _create_child(ctx, child, grandchild)
270
271
272@auth.required()
273def session_commit():
274 session = auth.lookup(request.headers.get('Authorization', None))
275 user = session['user']
276
277 req = request.get_json()
278 if not 'key' in req:
279 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
280 if not 'modifications' in req:
281 return(json.dumps({'success': False, 'error-msg': 'Missing modifications.'}))
282
283 mods = req['modifications']
284 ctx = sessions[user.username][req['key']]['session'].context
285 root = None
286 for key in mods:
287 recursion = False
288 # get correct path and value if needed
289 path = key
290 value = None
291 if mods[key]['type'] == 'change':
292 value = mods[key]['value']
293 elif mods[key]['type'] == 'create' or mods[key]['type'] == 'replace':
Radek Krejci7a244f02018-02-21 09:46:18 +0100294 if mods[key]['data']['info']['type'] == 1:
295 # creating/replacing container
296 recursion = True
297 elif mods[key]['data']['info']['type'] == 4:
Radek Krejcif041d652018-02-06 10:21:25 +0100298 # creating/replacing leaf
299 value = mods[key]['data']['value']
Radek Krejci7a244f02018-02-21 09:46:18 +0100300 elif mods[key]['data']['info']['type'] == 8:
301 # creating/replacing leaf-list
302 value = mods[key]['data']['value'][0]
303 path = mods[key]['data']['path']
Radek Krejcif041d652018-02-06 10:21:25 +0100304 elif mods[key]['data']['info']['type'] == 16:
305 recursion = True
306 path = mods[key]['data']['path']
Radek Krejcif041d652018-02-06 10:21:25 +0100307
308 # create node
Radek Krejci7a244f02018-02-21 09:46:18 +0100309 # print("creating " + path)
310 # print("value " + str(value))
Radek Krejcif041d652018-02-06 10:21:25 +0100311 if root:
312 root.new_path(ctx, path, value, 0, 0)
313 else:
Radek Krejci0f793fe2018-02-14 08:33:45 +0100314 root = yang.Data_Node(ctx, path, value, 0, 0)
Radek Krejcif041d652018-02-06 10:21:25 +0100315 node = root.find_path(path).data()[0];
316
317 # set operation attribute and add additional data if any
318 if mods[key]['type'] == 'change':
319 node.insert_attr(None, 'ietf-netconf:operation', 'merge')
320 elif mods[key]['type'] == 'delete':
321 node.insert_attr(None, 'ietf-netconf:operation', 'delete')
322 elif mods[key]['type'] == 'create':
323 node.insert_attr(None, 'ietf-netconf:operation', 'create')
324 elif mods[key]['type'] == 'replace':
325 node.insert_attr(None, 'ietf-netconf:operation', 'replace')
326 else:
327 return(json.dumps({'success': False, 'error-msg': 'Invalid modification ' + key}))
328
329 if recursion and 'children' in mods[key]['data']:
330 for child in mods[key]['data']['children']:
Radek Krejcif73403b2018-02-08 14:57:25 +0100331 if 'key' in child['info'] and child['info']['key']:
Radek Krejcif041d652018-02-06 10:21:25 +0100332 continue
333 _create_child(ctx, node, child)
334
Radek Krejci0f793fe2018-02-14 08:33:45 +0100335 # print(root.print_mem(yang.LYD_XML, yang.LYP_FORMAT))
Radek Krejcif73403b2018-02-08 14:57:25 +0100336 try:
337 sessions[user.username][req['key']]['session'].rpcEditConfig(nc.DATASTORE_RUNNING, root)
338 except nc.ReplyError as e:
339 reply = {'success': False, 'error': []}
340 for err in e.args[0]:
341 reply['error'].append(json.loads(str(err)))
342 return(json.dumps(reply))
343
Radek Krejcif041d652018-02-06 10:21:25 +0100344 return(json.dumps({'success': True}))
345
346
Radek Krejci2e578562017-10-17 11:11:13 +0200347@auth.required()
Radek Krejcicbbb1972017-09-21 13:25:19 +0200348def session_close():
349 session = auth.lookup(request.headers.get('Authorization', None))
350 user = session['user']
351 req = request.args.to_dict()
352
353 if not 'key' in req:
354 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
355
356 if not user.username in sessions:
357 sessions[user.username] = {}
358
359 key = req['key']
360 if not key in sessions[user.username]:
361 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
362
363 del sessions[user.username][key]
364 return(json.dumps({'success': True}))
365
366@auth.required()
367def session_alive():
368 session = auth.lookup(request.headers.get('Authorization', None))
369 user = session['user']
370 req = request.args.to_dict()
371
372 if not 'key' in req:
373 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
374
375 if not user.username in sessions:
376 sessions[user.username] = {}
377
378 key = req['key']
379 if not key in sessions[user.username]:
380 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
381
382 return(json.dumps({'success': True}))