blob: aced3f9476b409ae916436dc69c110bf676c3d98 [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# Code adapted from https://github.com/choppsv1/netconf
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
Khen Nursimulua4972742016-12-23 17:15:20 -050019import structlog
20from lxml import etree
21import netconf.nc_common.error as ncerror
22
23log = structlog.get_logger()
24
Khen Nursimulua7b842a2016-12-03 23:28:42 -050025
26class RpcResponse():
Khen Nursimulu8ffb8932017-01-26 13:40:49 -050027 def __init__(self, capabilities):
Khen Nursimulua7b842a2016-12-03 23:28:42 -050028 self.is_error = False
29 # if there is an error then the reply_node will contains an Error
30 # object
31 self.reply_node = None
Khen Nursimulua4972742016-12-23 17:15:20 -050032 self.close_session = False
Khen Nursimulu8ffb8932017-01-26 13:40:49 -050033 self.capabilities = capabilities
34 self.custom_rpc = False
Khen Nursimulua4972742016-12-23 17:15:20 -050035
Khen Nursimulu8ffb8932017-01-26 13:40:49 -050036 def build_xml_response(self, request, voltha_response, custom_rpc=False):
Khen Nursimulua4972742016-12-23 17:15:20 -050037 if request is None:
38 return
39 voltha_xml_string = etree.tostring(voltha_response)
40
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -050041 # Remove the leading and trailing <yang> tags
Khen Nursimulua4972742016-12-23 17:15:20 -050042 if voltha_xml_string.startswith('<yang>'):
43 voltha_xml_string = voltha_xml_string[len('<yang>'):]
44 if voltha_xml_string.endswith('</yang>'):
45 voltha_xml_string = voltha_xml_string[:-len('</yang>')]
Khen Nursimuluc7991dd2017-01-05 17:05:48 -050046 # Empty response
47 elif voltha_xml_string.startswith('<yang/>'):
Khen Nursimulub4e71472017-01-06 18:05:47 -050048 voltha_xml_string = ''
Khen Nursimulua4972742016-12-23 17:15:20 -050049
Khen Nursimulu3676b7c2017-01-31 13:48:38 -050050 # Replace any True/False data to true/false
51 voltha_xml_string = voltha_xml_string.replace('>False<', '>false<')
52 voltha_xml_string = voltha_xml_string.replace('>True<', '>true<')
53
Khen Nursimulu8ffb8932017-01-26 13:40:49 -050054 if not custom_rpc:
55 # Create the xml body as
56 if request.has_key('subclass'):
57 body = ''.join([
58 '<data>',
59 '<',
60 request['class'],
61 ' xmlns="',
62 request['namespace'],
63 '">',
64 '<',
65 request['subclass'],
66 '>',
67 voltha_xml_string,
68 '</',
69 request['subclass'],
70 '>',
71 '</',
72 request['class'],
73 '>',
74 '</data>'
75 ])
76 else:
77 body = ''.join([
78 '<data>',
79 '<',
80 request['class'],
Khen Nursimulu0b9aed12017-02-06 15:33:46 -050081 ' xmlns="urn:opencord:params:xml:ns:voltha:voltha">',
Khen Nursimulu8ffb8932017-01-26 13:40:49 -050082 voltha_xml_string,
83 '</',
84 request['class'],
85 '>',
86 '</data>'
87 ])
88 else: # custom_rpc
Khen Nursimulua4972742016-12-23 17:15:20 -050089 body = ''.join([
Khen Nursimulu8ffb8932017-01-26 13:40:49 -050090 '<rpc-reply',
Khen Nursimulu0b9aed12017-02-06 15:33:46 -050091 ' xmlns="urn:opencord:params:xml:ns:voltha:voltha">',
Khen Nursimulua4972742016-12-23 17:15:20 -050092 voltha_xml_string,
Khen Nursimulu8ffb8932017-01-26 13:40:49 -050093 '</rpc-reply>',
Khen Nursimulua4972742016-12-23 17:15:20 -050094 ])
95
96 return etree.fromstring(body)
97
98 def add_node(self, new_node, tree):
99 if new_node.tag == 'ignore':
100 # We want only sub-elements
101 for elem in list(new_node):
102 tree.append(elem)
103 else:
104 tree.append(new_node)
105
106 def copy_basic_element(self, elm):
107 new_elem = etree.Element(elm.tag)
108 new_elem.text = elm.text
109 return new_elem
110
111 def process_inline_option(self, elem):
112 inline_option = False
113 inline_node_name = None
114 for elm in list(elem):
115 if elm.tag == 'yang_field_option':
116 inline_option = True
117 if elm.tag == 'name':
118 inline_node_name = elm.text
119 if not inline_option:
120 new_elem = etree.Element(elem.tag)
121 return new_elem, elem
122
123 # look for the node with the inline_node_name
124 for elm in list(elem):
125 if elm.tag == inline_node_name:
126 new_elem = etree.Element('ignore')
127 return new_elem, elm
128
129 def process_element(self, elem):
130 attrib = elem.get('type')
131 if (attrib == 'list'):
132 if list(elem) is None:
133 return self.copy_basic_element(elem)
Khen Nursimulu3676b7c2017-01-31 13:48:38 -0500134 new_elem = etree.Element('ignore')
Khen Nursimulua4972742016-12-23 17:15:20 -0500135 for elm in list(elem):
136 elm.tag = elem.tag
137 if elm.get('type') in ['list', 'dict']:
138 self.add_node(self.process_element(elm), new_elem)
139 else:
140 new_elem.append(self.copy_basic_element(elm))
141 return new_elem
142 elif (attrib == 'dict'):
143 # Empty case
144 if list(elem) is None:
145 return self.copy_basic_element(elem)
146
147 # Process field option.
148 new_elem, elem = self.process_inline_option(elem)
149
150 for elm in list(elem):
151 if elm.get('type') in ['list', 'dict']:
152 self.add_node(self.process_element(elm), new_elem)
153 else:
154 new_elem.append(self.copy_basic_element(elm))
155 return new_elem
156 else:
157 return self.copy_basic_element(elem)
158
Khen Nursimulu3676b7c2017-01-31 13:48:38 -0500159 def to_yang_xml(self, from_xml, request, yang_options=None,
160 custom_rpc=False):
Khen Nursimulua4972742016-12-23 17:15:20 -0500161 # Parse from_xml as follows:
162 # 1. Any element having a list attribute shoud have each item move 1 level
163 # up and retag using the parent tag
164 # 2. Any element having a dict attribute and has a <yang_field_option>
165 # sub-element should have all it's items move to teh parent level
166 top = etree.Element('yang')
167 elms = list(from_xml)
Khen Nursimulu3676b7c2017-01-31 13:48:38 -0500168 xml_tag = yang_options[0]
169 list_items_name = yang_options[1]
Khen Nursimulub21bd642017-02-20 21:32:27 -0500170 # Handle the special case when the the xml contains 1 element which
171 # is a list type
Khen Nursimulu3676b7c2017-01-31 13:48:38 -0500172 if len(elms) == 1:
Khen Nursimulua4972742016-12-23 17:15:20 -0500173 item = elms[0]
174 if item.get('type') == 'list':
Khen Nursimulub21bd642017-02-20 21:32:27 -0500175 if custom_rpc: # custom rpc request
176 if list_items_name == 'items': # no pre-processing required
177 self.add_node(self.process_element(item), top)
178 return top
179 if xml_tag:
Khen Nursimulu3676b7c2017-01-31 13:48:38 -0500180 item.tag = xml_tag
Khen Nursimulub21bd642017-02-20 21:32:27 -0500181 else:
182 item.tag = 'ignore'
183 else:
184 # Default netconf operations - may end up needing
185 # specific parsing per request type
186 if request.has_key('subclass'):
Khen Nursimulu3676b7c2017-01-31 13:48:38 -0500187 item.tag = request['subclass']
188 # remove the subclass element in request to avoid duplicate tag
189 del request['subclass']
Khen Nursimulub21bd642017-02-20 21:32:27 -0500190 elif list_items_name == 'items':
191 item.tag = xml_tag
Khen Nursimulu3676b7c2017-01-31 13:48:38 -0500192 else:
193 item.tag = 'ignore'
Khen Nursimulub21bd642017-02-20 21:32:27 -0500194 self.add_node(self.process_element(item), top)
Khen Nursimulua4972742016-12-23 17:15:20 -0500195 return top
196
197 # Process normally for all other cases
198 for elm in elms:
199 self.add_node(self.process_element(elm), top)
200
201 return top
202
Khen Nursimulu3676b7c2017-01-31 13:48:38 -0500203 # Helper method to sort the xml message based on the xml tags
204 def sort_xml_response(self, xml):
205 for parent in xml.xpath('//*[./*]'): # Search for parent elements
206 parent[:] = sorted(parent, key=lambda x: x.tag)
207 return xml
208
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500209 # custom_rpc refers to custom RPCs different from Netconf default RPCs
210 # like get, get-config, edit-config, etc
Khen Nursimulu3676b7c2017-01-31 13:48:38 -0500211 def build_yang_response(self, root, request, yang_options=None,
212 custom_rpc=False):
Khen Nursimulua4972742016-12-23 17:15:20 -0500213 try:
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500214 self.custom_rpc = custom_rpc
Khen Nursimulu3676b7c2017-01-31 13:48:38 -0500215 yang_xml = self.to_yang_xml(root, request, yang_options,
216 custom_rpc)
Khen Nursimulua4972742016-12-23 17:15:20 -0500217 log.info('yang-xml', yang_xml=etree.tounicode(yang_xml,
218 pretty_print=True))
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500219 return self.build_xml_response(request, yang_xml, custom_rpc)
Khen Nursimulua4972742016-12-23 17:15:20 -0500220 except Exception as e:
Khen Nursimulu8ffb8932017-01-26 13:40:49 -0500221 log.exception('error-building-yang-response', request=request,
222 xml=etree.tostring(root))
Khen Nursimulua4972742016-12-23 17:15:20 -0500223 self.rpc_response.is_error = True
224 self.rpc_response.node = ncerror.BadMsg(request)
225 return