blob: 6b440e2688477114a8d7e9f8893f7e599708c193 [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 Nursimulu8ffb8932017-01-26 13:40:49 -050030
Khen Nursimulua7b842a2016-12-03 23:28:42 -050031class NetconfProtocolError(Exception): pass
32
33
34class NetconfProtocolHandler:
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -050035 def __init__(self, nc_server, nc_conn, session, grpc_client, capabilities):
Khen Nursimulua7b842a2016-12-03 23:28:42 -050036 self.started = True
37 self.conn = nc_conn
38 self.nc_server = nc_server
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050039 self.grpc_client = grpc_client
Khen Nursimulua7b842a2016-12-03 23:28:42 -050040 self.new_framing = False
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -050041 self.capabilities = capabilities
Khen Nursimulua7b842a2016-12-03 23:28:42 -050042 self.session = session
43 self.exiting = False
44 self.connected = Deferred()
45 self.connected.addCallback(self.nc_server.client_disconnected,
46 self, None)
47
48 def send_message(self, msg):
49 self.conn.send_msg(C.XML_HEADER + msg, self.new_framing)
50
51 def receive_message(self):
52 return self.conn.receive_msg_any(self.new_framing)
53
54 def send_hello(self, caplist, session=None):
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -050055 msg = elm(C.HELLO, attrib={C.XMLNS: ns(C.NC)})
Khen Nursimulua7b842a2016-12-03 23:28:42 -050056 caps = E.capabilities(*[E.capability(x) for x in caplist])
57 msg.append(caps)
58
59 if session is not None:
60 msg.append(E(C.SESSION_ID, str(session.session_id)))
61 msg = etree.tostring(msg)
62 log.info("Sending HELLO", msg=msg)
63 msg = msg.decode('utf-8')
64 self.send_message(msg)
65
66 def send_rpc_reply(self, rpc_reply, origmsg):
67 reply = etree.Element(qmap(C.NC) + C.RPC_REPLY, attrib=origmsg.attrib,
68 nsmap=origmsg.nsmap)
69 try:
70 rpc_reply.getchildren
71 reply.append(rpc_reply)
72 except AttributeError:
73 reply.extend(rpc_reply)
74 ucode = etree.tounicode(reply, pretty_print=True)
75 log.info("RPC-Reply", reply=ucode)
76 self.send_message(ucode)
77
Khen Nursimulu8ffb8932017-01-26 13:40:49 -050078 def send_custom_rpc_reply(self, rpc_reply, origmsg):
79 reply = etree.Element(qmap(C.NC) + C.RPC_REPLY, attrib=origmsg.attrib,
80 nsmap=rpc_reply.nsmap)
81 try:
82 reply.extend(rpc_reply.getchildren())
83 except AttributeError:
84 reply.extend(rpc_reply)
85 ucode = etree.tounicode(reply, pretty_print=True)
86 log.info("Custom-RPC-Reply", reply=ucode)
87 self.send_message(ucode)
88
Khen Nursimulua7b842a2016-12-03 23:28:42 -050089 def set_framing_version(self):
90 if C.NETCONF_BASE_11 in self.capabilities.client_caps:
91 self.new_framing = True
92 elif C.NETCONF_BASE_10 not in self.capabilities.client_caps:
93 raise SessionError(
94 "Client doesn't implement 1.0 or 1.1 of netconf")
95
96 @inlineCallbacks
97 def open_session(self):
98 # The transport should be connected at this point.
99 try:
100 # Send hello message.
101 yield self.send_hello(self.capabilities.server_caps, self.session)
102 # Get reply
103 reply = yield self.receive_message()
104 log.info("reply-received", reply=reply)
105
106 # Parse reply
107 tree = etree.parse(io.BytesIO(reply.encode('utf-8')))
108 root = tree.getroot()
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -0500109 caps = root.xpath(C.CAPABILITY_XPATH, namespaces=C.NS_MAP)
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500110
111 # Store capabilities
112 for cap in caps:
113 self.capabilities.add_client_capability(cap.text)
114
115 self.set_framing_version()
116 self.session.session_opened = True
117
118 log.info('session-opened', session_id=self.session.session_id,
119 framing="1.1" if self.new_framing else "1.0")
120 except Exception as e:
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500121 log.exception('hello-failure', e=e)
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500122 self.stop(repr(e))
123 raise
124
125 @inlineCallbacks
126 def start(self):
127 log.info('starting')
128
129 try:
130 yield self.open_session()
131 while True:
132 if not self.session.session_opened:
133 break;
134 msg = yield self.receive_message()
135 yield self.handle_request(msg)
136
137 except Exception as e:
138 log.exception('exception', exception=repr(e))
139 self.stop(repr(e))
140
141 log.info('shutdown')
142 returnValue(self)
143
144 @inlineCallbacks
145 def handle_request(self, msg):
146 if not self.session.session_opened:
147 return
148
149 # Any error with XML encoding here is going to cause a session close
150 try:
151 tree = etree.parse(io.BytesIO(msg.encode('utf-8')))
152 if not tree:
153 raise ncerror.SessionError(msg, "Invalid XML from client.")
154 except etree.XMLSyntaxError:
155 log.error("malformed-message", msg=msg)
156 try:
157 error = ncerror.BadMsg(msg)
158 self.send_message(error.get_reply_msg())
159 except AttributeError:
160 log.error("attribute-error", msg=msg)
161 # close session
162 self.close()
163 return
164
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -0500165 rpcs = tree.xpath(C.RPC_XPATH, namespaces=C.NS_MAP)
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500166 if not rpcs:
167 raise ncerror.SessionError(msg, "No rpc found")
168
169 # A message can have multiple rpc requests
170 rpc_factory = get_rpc_factory_instance()
171 for rpc in rpcs:
172 try:
173 # Validate message id is received
174 try:
175 msg_id = rpc.get(C.MESSAGE_ID)
176 log.info("Received-rpc-message-id", msg_id=msg_id)
177 except (TypeError, ValueError):
178 log.error('no-message-id', rpc=rpc)
179 raise ncerror.MissingElement(msg, C.MESSAGE_ID)
180
181 # Get a rpc handler
182 rpc_handler = rpc_factory.get_rpc_handler(rpc,
183 msg,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500184 self.grpc_client,
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -0500185 self.session,
186 self.capabilities)
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500187 if rpc_handler:
188 # set the parameters for this handler
189 response = yield rpc_handler.execute()
190 log.info('handler',
191 rpc_handler=rpc_handler,
192 is_error=response.is_error,
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500193 custom_rpc=response.custom_rpc,
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500194 response=response)
Khen Nursimuluc7991dd2017-01-05 17:05:48 -0500195 if not response.is_error:
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500196 if response.custom_rpc:
197 self.send_custom_rpc_reply(response.node, rpc)
198 else:
199 self.send_rpc_reply(response.node, rpc)
200 # self.send_rpc_reply(self.get_mock_volthainstance(), rpc)
Khen Nursimuluc7991dd2017-01-05 17:05:48 -0500201 else:
202 self.send_message(response.node.get_xml_reply())
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500203
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500204 if response.close_session:
205 log.info('response-closing-session', response=response)
206 self.close()
207 else:
208 log.error('no-rpc-handler',
209 request=msg,
210 session_id=self.session.session_id)
Khen Nursimulu5b7b3fb2017-01-13 16:00:00 -0500211 error = ncerror.NotImpl(rpc)
Khen Nursimuluc7991dd2017-01-05 17:05:48 -0500212 self.send_message(error.get_xml_reply())
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500213
214 except ncerror.BadMsg as err:
215 log.info('ncerror.BadMsg')
216 if self.new_framing:
Khen Nursimuluc7991dd2017-01-05 17:05:48 -0500217 self.send_message(err.get_xml_reply())
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500218 else:
219 # If we are 1.0 we have to simply close the connection
220 # as we are not allowed to send this error
221 log.error("Closing-1-0-session--malformed-message")
222 self.close()
223 except (ncerror.NotImpl, ncerror.MissingElement) as e:
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500224 log.exception('error', e=e)
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500225 self.send_message(e.get_reply_msg())
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500226 except Exception as e:
227 log.exception('Exception', e=e)
228 error = ncerror.ServerException(rpc, e)
Khen Nursimuluc7991dd2017-01-05 17:05:48 -0500229 self.send_message(error.get_xml_reply())
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500230
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500231 def stop(self, reason):
232 if not self.exiting:
233 log.debug('stopping')
234 self.exiting = True
235 if self.session.session_opened:
236 # TODO: send a closing message to the far end
237 self.conn.close_connection()
238 self.nc_server.session_mgr.remove_session(self.session)
239 self.session.session_opened = False
240 self.connected.callback(None)
241 log.info('stopped')
242
243 def close(self):
244 if not self.exiting:
245 log.debug('closing-client')
246 self.exiting = True
247 if self.session.session_opened:
248 self.conn.close_connection()
249 self.nc_server.session_mgr.remove_session(self.session)
250 self.session.session_opened = False
251 self.connected.callback(None)
252 log.info('closing-client')
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500253
254 # Example of a properly formatted Yang-XML message
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500255 def get_mock_volthainstance(self):
256 res = {'log_level': 'INFO',
257 'device_types': [
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500258 {'adapter': u'broadcom_onu',
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500259 'accepts_bulk_flow_update': True,
260 'id': u'broadcom_onu',
261 'accepts_add_remove_flow_updates': False
262 },
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500263 {'adapter': u'maple_olt',
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500264 'accepts_bulk_flow_update': True,
265 'id': u'maple_olt',
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500266 'accepts_add_remove_flow_updates': False
267 },
268 {'adapter': u'ponsim_olt',
269 'accepts_bulk_flow_update': True,
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500270 'id': u'ponsim_olt',
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500271 'accepts_add_remove_flow_updates': False
272 },
273 {'adapter': u'ponsim_onu',
274 'accepts_bulk_flow_update': True,
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500275 'id': u'ponsim_onu',
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500276 'accepts_add_remove_flow_updates': False
277 },
278 {'adapter': u'simulated_olt',
279 'accepts_bulk_flow_update': True,
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500280 'id': u'simulated_olt',
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500281 'accepts_add_remove_flow_updates': False
282 },
283 {'adapter': u'simulated_onu',
284 'accepts_bulk_flow_update': True,
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500285 'id': u'simulated_onu',
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500286 'accepts_add_remove_flow_updates': False
287 },
288 {'adapter': u'tibit_olt',
289 'accepts_bulk_flow_update': True,
290 'id': u'tibit_olt',
291 'accepts_add_remove_flow_updates': False
292 },
293 {'adapter': u'tibit_onu',
294 'accepts_bulk_flow_update': True,
295 'id': u'tibit_onu',
296 'accepts_add_remove_flow_updates': False}
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500297 ],
298 'logical_devices': [],
299 'devices': [],
300 'instance_id': u'compose_voltha_1',
301 'version': u'0.9.0',
302 'health': {'state': 'HEALTHY'},
303 'device_groups': [],
304 'adapters': [
305 {'config': {'log_level': 'INFO'},
306 'version': u'0.1',
307 'vendor': u'Voltha project',
308 'id': u'broadcom_onu',
309 'logical_device_ids': []
310 },
311 {'config': {'log_level': 'INFO'},
312 'version': u'0.1',
313 'vendor': u'Voltha project',
314 'id': u'maple_olt',
315 'logical_device_ids': []},
316 {'config': {'log_level': 'INFO'},
317 'version': u'0.4',
318 'vendor': u'Voltha project',
319 'id': u'ponsim_olt',
320 'logical_device_ids': []
321 },
322 {'config': {'log_level': 'INFO'},
323 'version': u'0.4',
324 'vendor': u'Voltha project',
325 'id': u'ponsim_onu',
326 'logical_device_ids': []
327 },
328 {'config': {'log_level': 'INFO'},
329 'version': u'0.1',
330 'vendor': u'Voltha project',
331 'id': u'simulated_olt',
332 'logical_device_ids': []
333 },
334 {'config': {'log_level': 'INFO'},
335 'version': u'0.1',
336 'vendor': u'Voltha project',
337 'id': u'simulated_onu',
338 'logical_device_ids': []
339 },
340 {'config': {'log_level': 'INFO'},
341 'version': u'0.1',
342 'vendor': u'Tibit Communications Inc.',
343 'id': u'tibit_olt',
344 'logical_device_ids': []
345 },
346 {'config': {'log_level': 'INFO'},
347 'version': u'0.1',
348 'vendor': u'Tibit Communications Inc.',
349 'id': u'tibit_onu',
350 'logical_device_ids': []
351 }
352 ]
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500353 }
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500354 devices_array = []
355 flow_items = []
356 for i in xrange(1, 10):
357 flow_items.append({
358 'items': {
359 'id': str(i),
360 'table_id': 'table_id_' + str(i),
361 'flags': i,
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500362 'instructions': [
363 {'type': i, 'goto_table': 'table_id_' + str(i)},
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500364 {'type': i, 'meter': i},
365 {'type': i,
366 'actions': {'actions': [
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500367 {'type': 11,
368 'output': {
369 'port': i,
370 'max_len': i}
371 }
372 ]}
Khen Nursimulufcdd45d2017-01-12 14:50:24 -0500373 }
374 ]
375 }
376 }
377 )
378 for i in xrange(1, 10):
379 devices_array.append({
380 'id': str(i),
381 'type': 'type_' + str(i),
382 'vlan': i,
383 'flows': flow_items
384 })
385 res['devices'] = devices_array
386 xml = dicttoxml.dicttoxml(res, attr_type=True)
387 root = etree.fromstring(xml)
388 # print etree.tounicode(root, pretty_print=True)
389 request = {'class': 'VolthaInstance'}
390 top = RpcResponse().build_yang_response(root, request)
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500391 return top