1) Handles chuncked xml requests from Netconf client, 2) Handles nested xml parameters from Netconf client, 3) Handles all currently exposed rpcs
Change-Id: Id478f355ab53afc876c4f5fd9ad8f1a048a50748
diff --git a/netconf/constants.py b/netconf/constants.py
index b45363f..704e1f8 100644
--- a/netconf/constants.py
+++ b/netconf/constants.py
@@ -107,6 +107,7 @@
MESSAGE_ID = "message-id"
XMLNS = "xmlns"
DELIMITER = "]]>]]>"
+ DELIMITER_1_1 ="##"
NS_MAP = {
'nc': 'urn:ietf:params:xml:ns:netconf:base:1.0',
diff --git a/netconf/grpc_client/grpc_client.py b/netconf/grpc_client/grpc_client.py
index cc52a27..bcfb972 100644
--- a/netconf/grpc_client/grpc_client.py
+++ b/netconf/grpc_client/grpc_client.py
@@ -306,15 +306,13 @@
# Get the XML tag to use in the response
xml_tag = mapper.get_xml_tag(service, method)
- # Get the XML list item name used in the response
- list_item_name = mapper.get_list_items_name(service, method)
+ # # Get the XML list item name used in the response
+ # list_item_name = mapper.get_list_items_name(service, method)
# Get the YANG defined fields (and their order) for that service
# and method
fields = mapper.get_fields_from_yang_defs(service, method)
- # TODO: This needs to be investigated further since the Netconf
- # Client shows a formatting error in the code below is uncommented.
# Check if this represents a List and whether the field name is
# items. In the response (a dictionary), if a list named 'items'
# is returned then 'items' can either:
@@ -324,10 +322,11 @@
# 2) represent the actual field name as defined in the proto
# definitions. If this is the case then we need to preserve the
# name
- # list_item_name = ''
- # if len(fields) == 1:
- # if fields[0]['name'] == 'items':
- # list_item_name = 'items'
+ list_item_name = ''
+ if fields: # if the return type is empty then fields will be None
+ if len(fields) == 1:
+ if fields[0]['name'] == 'items':
+ list_item_name = 'items'
# Rearrange the dictionary response as specified by the YANG
# definitions
diff --git a/netconf/nc_rpc/ext/voltha_rpc.py b/netconf/nc_rpc/ext/voltha_rpc.py
index 835f900..9d4bf33 100644
--- a/netconf/nc_rpc/ext/voltha_rpc.py
+++ b/netconf/nc_rpc/ext/voltha_rpc.py
@@ -83,17 +83,29 @@
self.request_xml)
return
+ # Helper function to parse a complex xml into a dict
+ def _parse_params(self, node):
+ params = {}
+ if node is not None:
+ for r in node:
+ children = r.getchildren()
+ if children:
+ params[ r.tag.replace(qmap(C.VOLTHA), "")] = \
+ self._parse_params(children)
+ else:
+ params[
+ r.tag.replace(qmap(C.VOLTHA), "")] = r.text
+ return params
+
def _extract_parameters(self):
try:
rpc_node = self.request_xml.find(''.join(
[qmap(C.VOLTHA),
self.request['command']])
)
- self.request['params'] = {}
- if rpc_node is not None:
- for r in rpc_node:
- self.request['params'][
- r.tag.replace(qmap(C.VOLTHA), "")] = r.text
+
+ # Parse rpc the parameters
+ self.request['params'] = self._parse_params(rpc_node)
# Remove the subclass element in the request if it is present as
# it is not required for rpc calls
diff --git a/netconf/nc_rpc/rpc_response.py b/netconf/nc_rpc/rpc_response.py
index 19e01e2..aced3f9 100644
--- a/netconf/nc_rpc/rpc_response.py
+++ b/netconf/nc_rpc/rpc_response.py
@@ -131,11 +131,7 @@
if (attrib == 'list'):
if list(elem) is None:
return self.copy_basic_element(elem)
- # if elem.tag == 'items':
- # new_elem = etree.Element('items')
new_elem = etree.Element('ignore')
- # else:
- # new_elem = etree.Element('ignore')
for elm in list(elem):
elm.tag = elem.tag
if elm.get('type') in ['list', 'dict']:
@@ -171,25 +167,31 @@
elms = list(from_xml)
xml_tag = yang_options[0]
list_items_name = yang_options[1]
- # special case the xml contain a list type
+ # Handle the special case when the the xml contains 1 element which
+ # is a list type
if len(elms) == 1:
item = elms[0]
if item.get('type') == 'list':
- if list_items_name == 'items':
- # Create a new parent element
- new_elem = etree.Element(xml_tag)
- self.add_node(self.process_element(item), new_elem)
- top.append(new_elem)
- else:
- if xml_tag and custom_rpc:
+ if custom_rpc: # custom rpc request
+ if list_items_name == 'items': # no pre-processing required
+ self.add_node(self.process_element(item), top)
+ return top
+ if xml_tag:
item.tag = xml_tag
- elif request.has_key('subclass'):
+ else:
+ item.tag = 'ignore'
+ else:
+ # Default netconf operations - may end up needing
+ # specific parsing per request type
+ if request.has_key('subclass'):
item.tag = request['subclass']
# remove the subclass element in request to avoid duplicate tag
del request['subclass']
+ elif list_items_name == 'items':
+ item.tag = xml_tag
else:
item.tag = 'ignore'
- self.add_node(self.process_element(item), top)
+ self.add_node(self.process_element(item), top)
return top
# Process normally for all other cases
diff --git a/netconf/session/nc_connection.py b/netconf/session/nc_connection.py
index 9043584..b0272a0 100644
--- a/netconf/session/nc_connection.py
+++ b/netconf/session/nc_connection.py
@@ -20,11 +20,13 @@
from twisted.internet.defer import inlineCallbacks, returnValue
from common.utils.message_queue import MessageQueue
from netconf.constants import Constants as C
+import re
log = structlog.get_logger()
MAXSSHBUF = C.MAXSSHBUF
+
class NetconfConnection(protocol.Protocol):
def __init__(self, data=None, avatar=None, max_chunk=MAXSSHBUF):
self.avatar = avatar
@@ -47,7 +49,7 @@
def dataReceived(self, data):
log.debug('data-received', len=len(data),
- received=hexdump(data, result='return'))
+ received=hexdump(data, result='return'))
assert len(data)
self.rx.put(data)
@@ -85,32 +87,52 @@
assert self.connected
msg = yield self.recv(lambda _: True)
if new_framing:
- returnValue(self._receive_11(msg))
+ response = yield self._receive_11(msg)
else:
- returnValue(self._receive_10(msg))
+ response = yield self._receive_10(msg)
+ returnValue(response)
+ @inlineCallbacks
def _receive_10(self, msg):
# search for message end indicator
searchfrom = 0
- eomidx = msg.find(C.DELIMITER, searchfrom)
- if eomidx != -1:
- log.info('received-msg', msg=msg[:eomidx])
- return msg[:eomidx]
- else:
- log.error('no-message-end-indicators', msg=msg)
- return msg
+ partial_msgs = []
+ while 1:
+ eomidx = msg.find(C.DELIMITER, searchfrom)
+ if eomidx != -1:
+ partial_msgs.append(msg[:eomidx])
+ full_msg = ''.join(partial_msgs)
+ log.info('full-msg-received', msg=full_msg)
+ returnValue(full_msg)
+ else:
+ partial_msgs.append(msg)
+ log.debug('partial-msg-received', msg=msg)
+ msg = yield self.recv(lambda _: True)
+ @inlineCallbacks
def _receive_11(self, msg):
- # Message is received in the format "\n#{len}\n{msg}\n##\n"
- # A message may have return characters within it
- if msg:
- log.info('received-msg-full', msg=msg)
+ # A message can be received in chunks where each chunk is formatted as:
+ # /\n#[len]\n
+ # \n##\n
+ # msg\n
+ # msg
+ # \n
+ # \nmsg\n
+ partial_msgs = []
+ while 1:
+ log.info('received-msg', msg=msg)
+ # Remove all reference to length if any, i.e any '#len'
+ msg = re.sub(r'#[0-9]+', "", msg)
msg = msg.split('\n')
- if len(msg) > 2:
- msg = ''.join(msg[2:(len(msg)-2)])
- log.info('parsed-msg\n', msg=msg)
- return msg
- return None
+ if C.DELIMITER_1_1 in msg: # The '##' is the second last ref
+ partial_msgs.append(''.join(msg[0:(len(msg) - 2)]))
+ full_msg = ''.join(partial_msgs)
+ log.debug('full-msg-received', msg=full_msg)
+ returnValue(full_msg)
+ else:
+ partial_msgs.append(''.join(msg))
+ log.debug('partial-msg-received', msg=msg)
+ msg = yield self.recv(lambda _: True)
def close_connection(self):
log.info('closing-connection')