blob: 968732ed86933d63a0a071f640ca28fdda058502 [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
12import netconf2 as nc
Radek Krejcicbbb1972017-09-21 13:25:19 +020013
14from .inventory import INVENTORY
15from .devices import devices_get
16from .error import NetopeerException
Radek Krejcia1339602017-11-02 13:52:38 +010017from .data import *
Radek Krejcicbbb1972017-09-21 13:25:19 +020018
19sessions = {}
20
Radek Krejcie20e4d82017-11-08 14:18:05 +010021def hostkey_check(hostname, state, keytype, hexa, priv):
22 # TODO real check
23 return True
24
Radek Krejcicbbb1972017-09-21 13:25:19 +020025@auth.required()
26def connect():
27 session = auth.lookup(request.headers.get('Authorization', None))
28 user = session['user']
29 path = os.path.join(INVENTORY, user.username)
30
Radek Krejcia6c8b412017-10-17 16:59:38 +020031 data = request.get_json()
32 if 'id' in data:
33 # stored device
34 device = devices_get(data['id'], user.username)
35 elif 'device' in data:
36 # one-time connect, the device is specified in request
37 device = data['device']
38 else:
39 raise NetopeerException('Invalid connection request.')
40
Radek Krejcicbbb1972017-09-21 13:25:19 +020041 if not device:
42 raise NetopeerException('Unknown device to connect to request.')
43
44 nc.setSearchpath(path)
45
46 ssh = nc.SSH(device['username'], password=device['password'])
Radek Krejcie20e4d82017-11-08 14:18:05 +010047 ssh.setAuthHostkeyCheckClb(hostkey_check)
Radek Krejcicbbb1972017-09-21 13:25:19 +020048 try:
49 session = nc.Session(device['hostname'], device['port'], ssh)
50 except Exception as e:
51 return(json.dumps({'success': False, 'error-msg': str(e)}))
52
53 if not user.username in sessions:
54 sessions[user.username] = {}
55
56 # use key (as hostname:port:session-id) to store the created NETCONF session
57 key = session.host + ":" + str(session.port) + ":" + session.id
Radek Krejci4d3896c2018-01-08 17:10:43 +010058 sessions[user.username][key] = {}
59 sessions[user.username][key]['session'] = session
Radek Krejcicbbb1972017-09-21 13:25:19 +020060
61 return(json.dumps({'success': True, 'session-key': key}))
62
63
64@auth.required()
65def session_get_capabilities():
66 session = auth.lookup(request.headers.get('Authorization', None))
67 user = session['user']
68 req = request.args.to_dict()
69
70 if not 'key' in req:
71 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
72
73 if not user.username in sessions:
74 sessions[user.username] = {}
75
76 key = req['key']
77 if not key in sessions[user.username]:
78 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
79
80 cpblts = []
Radek Krejci4d3896c2018-01-08 17:10:43 +010081 for c in sessions[user.username][key]['session'].capabilities:
Radek Krejcicbbb1972017-09-21 13:25:19 +020082 cpblts.append(c)
83
84 return(json.dumps({'success': True, 'capabilities': cpblts}))
85
86@auth.required()
Radek Krejci2e578562017-10-17 11:11:13 +020087def session_get():
88 session = auth.lookup(request.headers.get('Authorization', None))
89 user = session['user']
90 req = request.args.to_dict()
91
92 if not 'key' in req:
93 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
Radek Krejcia1339602017-11-02 13:52:38 +010094 if not 'recursive' in req:
95 return(json.dumps({'success': False, 'error-msg': 'Missing recursive flag.'}))
Radek Krejci2e578562017-10-17 11:11:13 +020096
97 if not user.username in sessions:
98 sessions[user.username] = {}
99
100 key = req['key']
101 if not key in sessions[user.username]:
102 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
103
Radek Krejci2e578562017-10-17 11:11:13 +0200104 try:
Radek Krejci4d3896c2018-01-08 17:10:43 +0100105 sessions[user.username][key]['data'] = sessions[user.username][key]['session'].rpcGet()
Radek Krejci2e578562017-10-17 11:11:13 +0200106 except nc.ReplyError as e:
107 reply = {'success': False, 'error': []}
108 for err in e.args[0]:
109 reply['error'].append(json.loads(str(err)))
110 return(json.dumps(reply))
111
Radek Krejcia1339602017-11-02 13:52:38 +0100112 if not 'path' in req:
Radek Krejci4d3896c2018-01-08 17:10:43 +0100113 return(dataInfoRoots(sessions[user.username][key]['data'], True if req['recursive'] == 'true' else False))
Radek Krejcia1339602017-11-02 13:52:38 +0100114 else:
Radek Krejci4d3896c2018-01-08 17:10:43 +0100115 return(dataInfoSubtree(sessions[user.username][key]['data'], req['path'], True if req['recursive'] == 'true' else False))
116
117
Radek Krejci6e772b22018-01-25 13:28:57 +0100118def _checkvalue(session, req, schema):
119 user = session['user'];
Radek Krejci4d3896c2018-01-08 17:10:43 +0100120
121 if not 'key' in req:
122 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
123 if not 'path' in req:
124 return(json.dumps({'success': False, 'error-msg': 'Missing path to validate value.'}))
125 if not 'value' in req:
126 return(json.dumps({'success': False, 'error-msg': 'Missing value to validate.'}))
127
Radek Krejci6e772b22018-01-25 13:28:57 +0100128 key = req['key']
129 if not key in sessions[user.username]:
130 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
131
132 if schema:
Radek Krejci6e772b22018-01-25 13:28:57 +0100133 search = sessions[user.username][key]['session'].context.find_path(req['path'])
Radek Krejci6e772b22018-01-25 13:28:57 +0100134 else:
135 search = sessions[user.username][key]['data'].find_path(req['path'])
136
137 if search.number() != 1:
138 return(json.dumps({'success': False, 'error-msg': 'Invalid data path.'}))
139
140 if schema:
141 node = search.schema()[0]
142 else:
143 node = search.data()[0]
144
145 if node.validate_value(req['value']):
146 return(json.dumps({'success': False, 'error-msg': ly.Error().errmsg()}))
147
148 return(json.dumps({'success': True}))
149
150@auth.required()
151def data_checkvalue():
152 session = auth.lookup(request.headers.get('Authorization', None))
153 req = request.args.to_dict()
154
155 return _checkvalue(session, req, False)
156
157
158@auth.required()
159def schema_checkvalue():
160 session = auth.lookup(request.headers.get('Authorization', None))
161 req = request.args.to_dict()
162
163 return _checkvalue(session, req, True)
164
165
166@auth.required()
167def schema_info():
168 session = auth.lookup(request.headers.get('Authorization', None))
169 user = session['user']
170 req = request.args.to_dict()
171
172 if not 'key' in req:
173 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
174 if not 'path' in req:
175 return(json.dumps({'success': False, 'error-msg': 'Missing path to validate value.'}))
Radek Krejci4d3896c2018-01-08 17:10:43 +0100176
177 key = req['key']
178 if not key in sessions[user.username]:
179 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
180
Radek Krejci6e772b22018-01-25 13:28:57 +0100181 if req['path'] == '/':
Radek Krejcif71867f2018-01-30 13:28:28 +0100182 node = None
Radek Krejci6e772b22018-01-25 13:28:57 +0100183 else:
184 search = sessions[user.username][key]['session'].context.find_path(req['path'])
185 if search.number() != 1:
186 return(json.dumps({'success': False, 'error-msg': 'Invalid data path.'}))
187 node = search.schema()[0]
Radek Krejci4d3896c2018-01-08 17:10:43 +0100188
Radek Krejci6e772b22018-01-25 13:28:57 +0100189 result = [];
190 if 'relative' in req:
191 if req['relative'] == 'children':
Radek Krejcif71867f2018-01-30 13:28:28 +0100192 if node:
193 instantiables = node.child_instantiables(0)
194 else:
195 # top level
196 instantiables = sessions[user.username][key]['session'].context.data_instantiables(0)
Radek Krejci6e772b22018-01-25 13:28:57 +0100197 elif req['relative'] == 'siblings':
198 if node.parent():
Radek Krejcif71867f2018-01-30 13:28:28 +0100199 instantiables = node.parent().child_instantiables(0)
200 else:
201 # top level
202 instantiables = sessions[user.username][key]['session'].context.data_instantiables(0)
Radek Krejci6e772b22018-01-25 13:28:57 +0100203 else:
204 return(json.dumps({'success': False, 'error-msg': 'Invalid relative parameter.'}))
Radek Krejcif71867f2018-01-30 13:28:28 +0100205
206 for child in instantiables:
207 if child.flags() & ly.LYS_CONFIG_R:
208 # ignore status nodes
209 continue
210 if child.nodetype() & (ly.LYS_RPC | ly.LYS_NOTIF | ly.LYS_ACTION):
211 # ignore RPCs, Notifications and Actions
212 continue
213 result.append(schemaInfoNode(child))
Radek Krejci6e772b22018-01-25 13:28:57 +0100214 else:
215 result.append(schemaInfoNode(node))
Radek Krejci4d3896c2018-01-08 17:10:43 +0100216
Radek Krejci6e772b22018-01-25 13:28:57 +0100217 return(json.dumps({'success': True, 'data': result}))
Radek Krejci2e578562017-10-17 11:11:13 +0200218
219
Radek Krejcif041d652018-02-06 10:21:25 +0100220def _create_child(ctx, parent, child_def):
221 module = ctx.get_module(child_def['info']['module'])
222 # print('child: ' + json.dumps(child_def))
223 if child_def['info']['type'] == 4:
224 # print('parent: ' + parent.schema().name())
225 # print('module: ' + module.name())
226 # print('name: ' + child_def['info']['name'])
227 # print('value: ' + child_def['value'])
228 ly.Data_Node(parent, module, child_def['info']['name'], child_def['value'])
229 else:
230 # print('parent: ' + parent.schema().name())
231 # print('module: ' + module.name())
232 # print('name: ' + child_def['info']['name'])
233 child = ly.Data_Node(parent, module, child_def['info']['name'])
234 if 'children' in child_def:
235 for grandchild in child_def['children']:
236 _create_child(ctx, child, grandchild)
237
238
239@auth.required()
240def session_commit():
241 session = auth.lookup(request.headers.get('Authorization', None))
242 user = session['user']
243
244 req = request.get_json()
245 if not 'key' in req:
246 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
247 if not 'modifications' in req:
248 return(json.dumps({'success': False, 'error-msg': 'Missing modifications.'}))
249
250 mods = req['modifications']
251 ctx = sessions[user.username][req['key']]['session'].context
252 root = None
253 for key in mods:
254 recursion = False
255 # get correct path and value if needed
256 path = key
257 value = None
258 if mods[key]['type'] == 'change':
259 value = mods[key]['value']
260 elif mods[key]['type'] == 'create' or mods[key]['type'] == 'replace':
261 if mods[key]['data']['info']['type'] == 4:
262 # creating/replacing leaf
263 value = mods[key]['data']['value']
264 elif mods[key]['data']['info']['type'] == 16:
265 recursion = True
266 path = mods[key]['data']['path']
267 elif mods[key]['data']['info']['type'] == 1:
268 recursion = True
269
270 # create node
271 if root:
272 root.new_path(ctx, path, value, 0, 0)
273 else:
274 root = ly.Data_Node(ctx, path, value, 0, 0)
275 node = root.find_path(path).data()[0];
276
277 # set operation attribute and add additional data if any
278 if mods[key]['type'] == 'change':
279 node.insert_attr(None, 'ietf-netconf:operation', 'merge')
280 elif mods[key]['type'] == 'delete':
281 node.insert_attr(None, 'ietf-netconf:operation', 'delete')
282 elif mods[key]['type'] == 'create':
283 node.insert_attr(None, 'ietf-netconf:operation', 'create')
284 elif mods[key]['type'] == 'replace':
285 node.insert_attr(None, 'ietf-netconf:operation', 'replace')
286 else:
287 return(json.dumps({'success': False, 'error-msg': 'Invalid modification ' + key}))
288
289 if recursion and 'children' in mods[key]['data']:
290 for child in mods[key]['data']['children']:
291 if 'key' in child['info']:
292 continue
293 _create_child(ctx, node, child)
294
295 print(root.print_mem(ly.LYD_XML, ly.LYP_FORMAT))
296 return(json.dumps({'success': True}))
297
298
Radek Krejci2e578562017-10-17 11:11:13 +0200299@auth.required()
Radek Krejcicbbb1972017-09-21 13:25:19 +0200300def session_close():
301 session = auth.lookup(request.headers.get('Authorization', None))
302 user = session['user']
303 req = request.args.to_dict()
304
305 if not 'key' in req:
306 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
307
308 if not user.username in sessions:
309 sessions[user.username] = {}
310
311 key = req['key']
312 if not key in sessions[user.username]:
313 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
314
315 del sessions[user.username][key]
316 return(json.dumps({'success': True}))
317
318@auth.required()
319def session_alive():
320 session = auth.lookup(request.headers.get('Authorization', None))
321 user = session['user']
322 req = request.args.to_dict()
323
324 if not 'key' in req:
325 return(json.dumps({'success': False, 'error-msg': 'Missing session key.'}))
326
327 if not user.username in sessions:
328 sessions[user.username] = {}
329
330 key = req['key']
331 if not key in sessions[user.username]:
332 return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'}))
333
334 return(json.dumps({'success': True}))