blob: fb0b13b4c06ab592c16b5cb2633f03bea1f13434 [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):
Radek Krejciad45e572018-02-21 10:54:54 +0100254 at = child_def['info']['module'].find('@')
255 if at == -1:
256 module = ctx.get_module(child_def['info']['module'])
257 else:
258 module = ctx.get_module(child_def['info']['module'][:at], child_def['info']['module'][at + 1:])
Radek Krejcif041d652018-02-06 10:21:25 +0100259 # print('child: ' + json.dumps(child_def))
Radek Krejci7a244f02018-02-21 09:46:18 +0100260 # print('parent: ' + parent.schema().name())
261 # print('module: ' + module.name())
262 # print('name: ' + child_def['info']['name'])
263 if child_def['info']['type'] == 4 :
264 # print('value: ' + str(child_def['value']))
Radek Krejci0f793fe2018-02-14 08:33:45 +0100265 yang.Data_Node(parent, module, child_def['info']['name'], child_def['value'])
Radek Krejci7a244f02018-02-21 09:46:18 +0100266 elif child_def['info']['type'] == 8:
267 # print('value: ' + child_def['value'][0])
268 yang.Data_Node(parent, module, child_def['info']['name'], child_def['value'][0])
Radek Krejcif041d652018-02-06 10:21:25 +0100269 else:
Radek Krejci0f793fe2018-02-14 08:33:45 +0100270 child = yang.Data_Node(parent, module, child_def['info']['name'])
Radek Krejcif041d652018-02-06 10:21:25 +0100271 if 'children' in child_def:
272 for grandchild in child_def['children']:
273 _create_child(ctx, child, grandchild)
274
275
276@auth.required()
277def session_commit():
278 session = auth.lookup(request.headers.get('Authorization', None))
279 user = session['user']
280
281 req = request.get_json()
282 if not 'key' in req:
283 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
284 if not 'modifications' in req:
285 return(json.dumps({'success': False, 'error-msg': 'Missing modifications.'}))
286
287 mods = req['modifications']
288 ctx = sessions[user.username][req['key']]['session'].context
289 root = None
290 for key in mods:
291 recursion = False
292 # get correct path and value if needed
293 path = key
294 value = None
295 if mods[key]['type'] == 'change':
296 value = mods[key]['value']
297 elif mods[key]['type'] == 'create' or mods[key]['type'] == 'replace':
Radek Krejci7a244f02018-02-21 09:46:18 +0100298 if mods[key]['data']['info']['type'] == 1:
299 # creating/replacing container
300 recursion = True
301 elif mods[key]['data']['info']['type'] == 4:
Radek Krejcif041d652018-02-06 10:21:25 +0100302 # creating/replacing leaf
303 value = mods[key]['data']['value']
Radek Krejci7a244f02018-02-21 09:46:18 +0100304 elif mods[key]['data']['info']['type'] == 8:
305 # creating/replacing leaf-list
306 value = mods[key]['data']['value'][0]
307 path = mods[key]['data']['path']
Radek Krejcif041d652018-02-06 10:21:25 +0100308 elif mods[key]['data']['info']['type'] == 16:
309 recursion = True
310 path = mods[key]['data']['path']
Radek Krejcif041d652018-02-06 10:21:25 +0100311
312 # create node
Radek Krejci7a244f02018-02-21 09:46:18 +0100313 # print("creating " + path)
314 # print("value " + str(value))
Radek Krejcif041d652018-02-06 10:21:25 +0100315 if root:
316 root.new_path(ctx, path, value, 0, 0)
317 else:
Radek Krejci0f793fe2018-02-14 08:33:45 +0100318 root = yang.Data_Node(ctx, path, value, 0, 0)
Radek Krejcif041d652018-02-06 10:21:25 +0100319 node = root.find_path(path).data()[0];
320
321 # set operation attribute and add additional data if any
322 if mods[key]['type'] == 'change':
323 node.insert_attr(None, 'ietf-netconf:operation', 'merge')
324 elif mods[key]['type'] == 'delete':
325 node.insert_attr(None, 'ietf-netconf:operation', 'delete')
326 elif mods[key]['type'] == 'create':
327 node.insert_attr(None, 'ietf-netconf:operation', 'create')
328 elif mods[key]['type'] == 'replace':
329 node.insert_attr(None, 'ietf-netconf:operation', 'replace')
330 else:
331 return(json.dumps({'success': False, 'error-msg': 'Invalid modification ' + key}))
332
333 if recursion and 'children' in mods[key]['data']:
334 for child in mods[key]['data']['children']:
Radek Krejcif73403b2018-02-08 14:57:25 +0100335 if 'key' in child['info'] and child['info']['key']:
Radek Krejcif041d652018-02-06 10:21:25 +0100336 continue
337 _create_child(ctx, node, child)
338
Radek Krejci0f793fe2018-02-14 08:33:45 +0100339 # print(root.print_mem(yang.LYD_XML, yang.LYP_FORMAT))
Radek Krejcif73403b2018-02-08 14:57:25 +0100340 try:
341 sessions[user.username][req['key']]['session'].rpcEditConfig(nc.DATASTORE_RUNNING, root)
342 except nc.ReplyError as e:
343 reply = {'success': False, 'error': []}
344 for err in e.args[0]:
345 reply['error'].append(json.loads(str(err)))
346 return(json.dumps(reply))
347
Radek Krejcif041d652018-02-06 10:21:25 +0100348 return(json.dumps({'success': True}))
349
350
Radek Krejci2e578562017-10-17 11:11:13 +0200351@auth.required()
Radek Krejcicbbb1972017-09-21 13:25:19 +0200352def session_close():
353 session = auth.lookup(request.headers.get('Authorization', None))
354 user = session['user']
355 req = request.args.to_dict()
356
357 if not 'key' in req:
358 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
359
360 if not user.username in sessions:
361 sessions[user.username] = {}
362
363 key = req['key']
364 if not key in sessions[user.username]:
365 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
366
367 del sessions[user.username][key]
368 return(json.dumps({'success': True}))
369
370@auth.required()
371def session_alive():
372 session = auth.lookup(request.headers.get('Authorization', None))
373 user = session['user']
374 req = request.args.to_dict()
375
376 if not 'key' in req:
377 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
378
379 if not user.username in sessions:
380 sessions[user.username] = {}
381
382 key = req['key']
383 if not key in sessions[user.username]:
384 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
385
386 return(json.dumps({'success': True}))