Khen Nursimulu | a7b842a | 2016-12-03 23:28:42 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
Khen Nursimulu | c9ef7c1 | 2017-01-04 20:40:53 -0500 | [diff] [blame] | 3 | # Copyright 2017 the original author or authors. |
Khen Nursimulu | a7b842a | 2016-12-03 23:28:42 -0500 | [diff] [blame] | 4 | # |
| 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 Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 19 | import structlog |
| 20 | from lxml import etree |
| 21 | import netconf.nc_common.error as ncerror |
| 22 | |
| 23 | log = structlog.get_logger() |
| 24 | |
Khen Nursimulu | a7b842a | 2016-12-03 23:28:42 -0500 | [diff] [blame] | 25 | |
| 26 | class RpcResponse(): |
Khen Nursimulu | 8ffb893 | 2017-01-26 13:40:49 -0500 | [diff] [blame] | 27 | def __init__(self, capabilities): |
Khen Nursimulu | a7b842a | 2016-12-03 23:28:42 -0500 | [diff] [blame] | 28 | 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 Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 32 | self.close_session = False |
Khen Nursimulu | 8ffb893 | 2017-01-26 13:40:49 -0500 | [diff] [blame] | 33 | self.capabilities = capabilities |
| 34 | self.custom_rpc = False |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 35 | |
Khen Nursimulu | 8ffb893 | 2017-01-26 13:40:49 -0500 | [diff] [blame] | 36 | def build_xml_response(self, request, voltha_response, custom_rpc=False): |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 37 | if request is None: |
| 38 | return |
| 39 | voltha_xml_string = etree.tostring(voltha_response) |
| 40 | |
Khen Nursimulu | c9ef7c1 | 2017-01-04 20:40:53 -0500 | [diff] [blame] | 41 | # Remove the leading and trailing <yang> tags |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 42 | 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 Nursimulu | c7991dd | 2017-01-05 17:05:48 -0500 | [diff] [blame] | 46 | # Empty response |
| 47 | elif voltha_xml_string.startswith('<yang/>'): |
Khen Nursimulu | b4e7147 | 2017-01-06 18:05:47 -0500 | [diff] [blame] | 48 | voltha_xml_string = '' |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 49 | |
Khen Nursimulu | 3676b7c | 2017-01-31 13:48:38 -0500 | [diff] [blame] | 50 | # 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 Nursimulu | 8ffb893 | 2017-01-26 13:40:49 -0500 | [diff] [blame] | 54 | 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 Nursimulu | 0b9aed1 | 2017-02-06 15:33:46 -0500 | [diff] [blame] | 81 | ' xmlns="urn:opencord:params:xml:ns:voltha:voltha">', |
Khen Nursimulu | 8ffb893 | 2017-01-26 13:40:49 -0500 | [diff] [blame] | 82 | voltha_xml_string, |
| 83 | '</', |
| 84 | request['class'], |
| 85 | '>', |
| 86 | '</data>' |
| 87 | ]) |
| 88 | else: # custom_rpc |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 89 | body = ''.join([ |
Khen Nursimulu | 8ffb893 | 2017-01-26 13:40:49 -0500 | [diff] [blame] | 90 | '<rpc-reply', |
Khen Nursimulu | 0b9aed1 | 2017-02-06 15:33:46 -0500 | [diff] [blame] | 91 | ' xmlns="urn:opencord:params:xml:ns:voltha:voltha">', |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 92 | voltha_xml_string, |
Khen Nursimulu | 8ffb893 | 2017-01-26 13:40:49 -0500 | [diff] [blame] | 93 | '</rpc-reply>', |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 94 | ]) |
| 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 Nursimulu | 3676b7c | 2017-01-31 13:48:38 -0500 | [diff] [blame] | 134 | new_elem = etree.Element('ignore') |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 135 | 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 Nursimulu | 3676b7c | 2017-01-31 13:48:38 -0500 | [diff] [blame] | 159 | def to_yang_xml(self, from_xml, request, yang_options=None, |
| 160 | custom_rpc=False): |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 161 | # 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 Nursimulu | 3676b7c | 2017-01-31 13:48:38 -0500 | [diff] [blame] | 168 | xml_tag = yang_options[0] |
| 169 | list_items_name = yang_options[1] |
Khen Nursimulu | b21bd64 | 2017-02-20 21:32:27 -0500 | [diff] [blame] | 170 | # Handle the special case when the the xml contains 1 element which |
| 171 | # is a list type |
Khen Nursimulu | 3676b7c | 2017-01-31 13:48:38 -0500 | [diff] [blame] | 172 | if len(elms) == 1: |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 173 | item = elms[0] |
| 174 | if item.get('type') == 'list': |
Khen Nursimulu | b21bd64 | 2017-02-20 21:32:27 -0500 | [diff] [blame] | 175 | 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 Nursimulu | 3676b7c | 2017-01-31 13:48:38 -0500 | [diff] [blame] | 180 | item.tag = xml_tag |
Khen Nursimulu | b21bd64 | 2017-02-20 21:32:27 -0500 | [diff] [blame] | 181 | 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 Nursimulu | 3676b7c | 2017-01-31 13:48:38 -0500 | [diff] [blame] | 187 | item.tag = request['subclass'] |
| 188 | # remove the subclass element in request to avoid duplicate tag |
| 189 | del request['subclass'] |
Khen Nursimulu | b21bd64 | 2017-02-20 21:32:27 -0500 | [diff] [blame] | 190 | elif list_items_name == 'items': |
| 191 | item.tag = xml_tag |
Khen Nursimulu | 3676b7c | 2017-01-31 13:48:38 -0500 | [diff] [blame] | 192 | else: |
| 193 | item.tag = 'ignore' |
Khen Nursimulu | b21bd64 | 2017-02-20 21:32:27 -0500 | [diff] [blame] | 194 | self.add_node(self.process_element(item), top) |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 195 | 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 Nursimulu | 3676b7c | 2017-01-31 13:48:38 -0500 | [diff] [blame] | 203 | # 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 Nursimulu | 8ffb893 | 2017-01-26 13:40:49 -0500 | [diff] [blame] | 209 | # custom_rpc refers to custom RPCs different from Netconf default RPCs |
| 210 | # like get, get-config, edit-config, etc |
Khen Nursimulu | 3676b7c | 2017-01-31 13:48:38 -0500 | [diff] [blame] | 211 | def build_yang_response(self, root, request, yang_options=None, |
| 212 | custom_rpc=False): |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 213 | try: |
Khen Nursimulu | 8ffb893 | 2017-01-26 13:40:49 -0500 | [diff] [blame] | 214 | self.custom_rpc = custom_rpc |
Khen Nursimulu | 3676b7c | 2017-01-31 13:48:38 -0500 | [diff] [blame] | 215 | yang_xml = self.to_yang_xml(root, request, yang_options, |
| 216 | custom_rpc) |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 217 | log.info('yang-xml', yang_xml=etree.tounicode(yang_xml, |
| 218 | pretty_print=True)) |
Khen Nursimulu | 8ffb893 | 2017-01-26 13:40:49 -0500 | [diff] [blame] | 219 | return self.build_xml_response(request, yang_xml, custom_rpc) |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 220 | except Exception as e: |
Khen Nursimulu | 8ffb893 | 2017-01-26 13:40:49 -0500 | [diff] [blame] | 221 | log.exception('error-building-yang-response', request=request, |
| 222 | xml=etree.tostring(root)) |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 223 | self.rpc_response.is_error = True |
| 224 | self.rpc_response.node = ncerror.BadMsg(request) |
| 225 | return |