blob: 441c29c417668f9cc6c361ceb48bc4741c1eaace [file] [log] [blame]
Khen Nursimulua7b842a2016-12-03 23:28:42 -05001#!/usr/bin/env python
2#
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -05003# Copyright 2017 the original author or authors.
Khen Nursimulua7b842a2016-12-03 23:28:42 -05004#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17import structlog
18import io
19from lxml import etree
20from lxml.builder import E
Khen Nursimulua7b842a2016-12-03 23:28:42 -050021from twisted.internet.defer import inlineCallbacks, returnValue, Deferred
Khen Nursimulua7b842a2016-12-03 23:28:42 -050022from netconf.nc_rpc.rpc_factory import get_rpc_factory_instance
23from netconf.constants import Constants as C
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -050024from netconf.nc_common.utils import qmap, ns, elm
25import netconf.nc_common.error as ncerror
Khen Nursimulufcdd45d2017-01-12 14:50:24 -050026from netconf.nc_rpc.rpc_response import RpcResponse
Khen Nursimulua7b842a2016-12-03 23:28:42 -050027
28log = structlog.get_logger()
29
Khen Nursimulua7b842a2016-12-03 23:28:42 -050030class NetconfProtocolError(Exception): pass
31
32
33class NetconfProtocolHandler:
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -050034 def __init__(self, nc_server, nc_conn, session, grpc_client, capabilities):
Khen Nursimulua7b842a2016-12-03 23:28:42 -050035 self.started = True
36 self.conn = nc_conn
37 self.nc_server = nc_server
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050038 self.grpc_client = grpc_client
Khen Nursimulua7b842a2016-12-03 23:28:42 -050039 self.new_framing = False
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -050040 self.capabilities = capabilities
Khen Nursimulua7b842a2016-12-03 23:28:42 -050041 self.session = session
42 self.exiting = False
43 self.connected = Deferred()
44 self.connected.addCallback(self.nc_server.client_disconnected,
45 self, None)
46
47 def send_message(self, msg):
48 self.conn.send_msg(C.XML_HEADER + msg, self.new_framing)
49
50 def receive_message(self):
51 return self.conn.receive_msg_any(self.new_framing)
52
53 def send_hello(self, caplist, session=None):
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -050054 msg = elm(C.HELLO, attrib={C.XMLNS: ns(C.NC)})
Khen Nursimulua7b842a2016-12-03 23:28:42 -050055 caps = E.capabilities(*[E.capability(x) for x in caplist])
56 msg.append(caps)
57
58 if session is not None:
59 msg.append(E(C.SESSION_ID, str(session.session_id)))
60 msg = etree.tostring(msg)
61 log.info("Sending HELLO", msg=msg)
62 msg = msg.decode('utf-8')
63 self.send_message(msg)
64
65 def send_rpc_reply(self, rpc_reply, origmsg):
66 reply = etree.Element(qmap(C.NC) + C.RPC_REPLY, attrib=origmsg.attrib,
67 nsmap=origmsg.nsmap)
68 try:
69 rpc_reply.getchildren
70 reply.append(rpc_reply)
71 except AttributeError:
72 reply.extend(rpc_reply)
73 ucode = etree.tounicode(reply, pretty_print=True)
74 log.info("RPC-Reply", reply=ucode)
75 self.send_message(ucode)
76
77 def set_framing_version(self):
78 if C.NETCONF_BASE_11 in self.capabilities.client_caps:
79 self.new_framing = True
80 elif C.NETCONF_BASE_10 not in self.capabilities.client_caps:
81 raise SessionError(
82 "Client doesn't implement 1.0 or 1.1 of netconf")
83
84 @inlineCallbacks
85 def open_session(self):
86 # The transport should be connected at this point.
87 try:
88 # Send hello message.
89 yield self.send_hello(self.capabilities.server_caps, self.session)
90 # Get reply
91 reply = yield self.receive_message()
92 log.info("reply-received", reply=reply)
93
94 # Parse reply
95 tree = etree.parse(io.BytesIO(reply.encode('utf-8')))
96 root = tree.getroot()
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -050097 caps = root.xpath(C.CAPABILITY_XPATH, namespaces=C.NS_MAP)
Khen Nursimulua7b842a2016-12-03 23:28:42 -050098
99 # Store capabilities
100 for cap in caps:
101 self.capabilities.add_client_capability(cap.text)
102
103 self.set_framing_version()
104 self.session.session_opened = True
105
106 log.info('session-opened', session_id=self.session.session_id,
107 framing="1.1" if self.new_framing else "1.0")
108 except Exception as e:
109 log.error('hello-failure', exception=repr(e))
110 self.stop(repr(e))
111 raise
112
113 @inlineCallbacks
114 def start(self):
115 log.info('starting')
116
117 try:
118 yield self.open_session()
119 while True:
120 if not self.session.session_opened:
121 break;
122 msg = yield self.receive_message()
123 yield self.handle_request(msg)
124
125 except Exception as e:
126 log.exception('exception', exception=repr(e))
127 self.stop(repr(e))
128
129 log.info('shutdown')
130 returnValue(self)
131
132 @inlineCallbacks
133 def handle_request(self, msg):
134 if not self.session.session_opened:
135 return
136
137 # Any error with XML encoding here is going to cause a session close
138 try:
139 tree = etree.parse(io.BytesIO(msg.encode('utf-8')))
140 if not tree:
141 raise ncerror.SessionError(msg, "Invalid XML from client.")
142 except etree.XMLSyntaxError:
143 log.error("malformed-message", msg=msg)
144 try:
145 error = ncerror.BadMsg(msg)
146 self.send_message(error.get_reply_msg())
147 except AttributeError:
148 log.error("attribute-error", msg=msg)
149 # close session
150 self.close()
151 return
152
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -0500153 rpcs = tree.xpath(C.RPC_XPATH, namespaces=C.NS_MAP)
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500154 if not rpcs:
155 raise ncerror.SessionError(msg, "No rpc found")
156
157 # A message can have multiple rpc requests
158 rpc_factory = get_rpc_factory_instance()
159 for rpc in rpcs:
160 try:
161 # Validate message id is received
162 try:
163 msg_id = rpc.get(C.MESSAGE_ID)
164 log.info("Received-rpc-message-id", msg_id=msg_id)
165 except (TypeError, ValueError):
166 log.error('no-message-id', rpc=rpc)
167 raise ncerror.MissingElement(msg, C.MESSAGE_ID)
168
169 # Get a rpc handler
170 rpc_handler = rpc_factory.get_rpc_handler(rpc,
171 msg,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500172 self.grpc_client,
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -0500173 self.session,
174 self.capabilities)
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500175 if rpc_handler:
176 # set the parameters for this handler
177 response = yield rpc_handler.execute()
178 log.info('handler',
179 rpc_handler=rpc_handler,
180 is_error=response.is_error,
181 response=response)
Khen Nursimuluc7991dd2017-01-05 17:05:48 -0500182 if not response.is_error:
183 self.send_rpc_reply(response.node, rpc)
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500184 # self.send_rpc_reply(self.get_mock_volthainstance(), rpc)
Khen Nursimuluc7991dd2017-01-05 17:05:48 -0500185 else:
186 self.send_message(response.node.get_xml_reply())
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500187
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500188 if response.close_session:
189 log.info('response-closing-session', response=response)
190 self.close()
191 else:
192 log.error('no-rpc-handler',
193 request=msg,
194 session_id=self.session.session_id)
Khen Nursimulu5b7b3fb2017-01-13 16:00:00 -0500195 error = ncerror.NotImpl(rpc)
Khen Nursimuluc7991dd2017-01-05 17:05:48 -0500196 self.send_message(error.get_xml_reply())
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500197
198 except ncerror.BadMsg as err:
199 log.info('ncerror.BadMsg')
200 if self.new_framing:
Khen Nursimuluc7991dd2017-01-05 17:05:48 -0500201 self.send_message(err.get_xml_reply())
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500202 else:
203 # If we are 1.0 we have to simply close the connection
204 # as we are not allowed to send this error
205 log.error("Closing-1-0-session--malformed-message")
206 self.close()
207 except (ncerror.NotImpl, ncerror.MissingElement) as e:
208 log.info('error', repr(e))
209 self.send_message(e.get_reply_msg())
210 except Exception as ex:
211 log.info('Exception', repr(ex))
212 error = ncerror.ServerException(rpc, ex)
Khen Nursimuluc7991dd2017-01-05 17:05:48 -0500213 self.send_message(error.get_xml_reply())
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500214
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500215
216 def stop(self, reason):
217 if not self.exiting:
218 log.debug('stopping')
219 self.exiting = True
220 if self.session.session_opened:
221 # TODO: send a closing message to the far end
222 self.conn.close_connection()
223 self.nc_server.session_mgr.remove_session(self.session)
224 self.session.session_opened = False
225 self.connected.callback(None)
226 log.info('stopped')
227
228 def close(self):
229 if not self.exiting:
230 log.debug('closing-client')
231 self.exiting = True
232 if self.session.session_opened:
233 self.conn.close_connection()
234 self.nc_server.session_mgr.remove_session(self.session)
235 self.session.session_opened = False
236 self.connected.callback(None)
237 log.info('closing-client')
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500238
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500239
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500240 # Example of a properly formatted Yang-XML message
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500241 def get_mock_volthainstance(self):
242 res = {'log_level': 'INFO',
243 'device_types': [
244 {'adapter': u'broadcom_onu',
245 'accepts_bulk_flow_update': True,
246 'id': u'broadcom_onu',
247 'accepts_add_remove_flow_updates': False
248 },
249 {'adapter': u'maple_olt',
250 'accepts_bulk_flow_update': True,
251 'id': u'maple_olt',
252 'accepts_add_remove_flow_updates': False
253 },
254 {'adapter': u'ponsim_olt',
255 'accepts_bulk_flow_update': True,
256 'id': u'ponsim_olt',
257 'accepts_add_remove_flow_updates': False
258 },
259 {'adapter': u'ponsim_onu',
260 'accepts_bulk_flow_update': True,
261 'id': u'ponsim_onu',
262 'accepts_add_remove_flow_updates': False
263 },
264 {'adapter': u'simulated_olt',
265 'accepts_bulk_flow_update': True,
266 'id': u'simulated_olt',
267 'accepts_add_remove_flow_updates': False
268 },
269 {'adapter': u'simulated_onu',
270 'accepts_bulk_flow_update': True,
271 'id': u'simulated_onu',
272 'accepts_add_remove_flow_updates': False
273 },
274 {'adapter': u'tibit_olt',
275 'accepts_bulk_flow_update': True,
276 'id': u'tibit_olt',
277 'accepts_add_remove_flow_updates': False
278 },
279 {'adapter': u'tibit_onu',
280 'accepts_bulk_flow_update': True,
281 'id': u'tibit_onu',
282 'accepts_add_remove_flow_updates': False}
283 ],
284 'logical_devices': [],
285 'devices': [],
286 'instance_id': u'compose_voltha_1',
287 'version': u'0.9.0',
288 'health': {'state': 'HEALTHY'},
289 'device_groups': [],
290 'adapters': [
291 {'config': {'log_level': 'INFO'},
292 'version': u'0.1',
293 'vendor': u'Voltha project',
294 'id': u'broadcom_onu',
295 'logical_device_ids': []
296 },
297 {'config': {'log_level': 'INFO'},
298 'version': u'0.1',
299 'vendor': u'Voltha project',
300 'id': u'maple_olt',
301 'logical_device_ids': []},
302 {'config': {'log_level': 'INFO'},
303 'version': u'0.4',
304 'vendor': u'Voltha project',
305 'id': u'ponsim_olt',
306 'logical_device_ids': []
307 },
308 {'config': {'log_level': 'INFO'},
309 'version': u'0.4',
310 'vendor': u'Voltha project',
311 'id': u'ponsim_onu',
312 'logical_device_ids': []
313 },
314 {'config': {'log_level': 'INFO'},
315 'version': u'0.1',
316 'vendor': u'Voltha project',
317 'id': u'simulated_olt',
318 'logical_device_ids': []
319 },
320 {'config': {'log_level': 'INFO'},
321 'version': u'0.1',
322 'vendor': u'Voltha project',
323 'id': u'simulated_onu',
324 'logical_device_ids': []
325 },
326 {'config': {'log_level': 'INFO'},
327 'version': u'0.1',
328 'vendor': u'Tibit Communications Inc.',
329 'id': u'tibit_olt',
330 'logical_device_ids': []
331 },
332 {'config': {'log_level': 'INFO'},
333 'version': u'0.1',
334 'vendor': u'Tibit Communications Inc.',
335 'id': u'tibit_onu',
336 'logical_device_ids': []
337 }
338 ]
339 }
340 devices_array = []
341 flow_items = []
342 for i in xrange(1, 10):
343 flow_items.append({
344 'items': {
345 'id': str(i),
346 'table_id': 'table_id_' + str(i),
347 'flags': i,
348 'instructions' : [
349 {'type' : i, 'goto_table': 'table_id_' + str(i) },
350 {'type': i, 'meter': i},
351 {'type': i,
352 'actions': {'actions': [
353 {'type': 11,
354 'output': {
355 'port': i,
356 'max_len': i}
357 }
358 ]}
359 }
360 ]
361 }
362 }
363 )
364 for i in xrange(1, 10):
365 devices_array.append({
366 'id': str(i),
367 'type': 'type_' + str(i),
368 'vlan': i,
369 'flows': flow_items
370 })
371 res['devices'] = devices_array
372 xml = dicttoxml.dicttoxml(res, attr_type=True)
373 root = etree.fromstring(xml)
374 # print etree.tounicode(root, pretty_print=True)
375 request = {'class': 'VolthaInstance'}
376 top = RpcResponse().build_yang_response(root, request)
377 return top