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(): |
| 27 | def __init__(self): |
| 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 |
| 33 | |
| 34 | def build_xml_response(self, request, voltha_response): |
| 35 | if request is None: |
| 36 | return |
| 37 | voltha_xml_string = etree.tostring(voltha_response) |
| 38 | |
Khen Nursimulu | c9ef7c1 | 2017-01-04 20:40:53 -0500 | [diff] [blame] | 39 | # Remove the leading and trailing <yang> tags |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 40 | if voltha_xml_string.startswith('<yang>'): |
| 41 | voltha_xml_string = voltha_xml_string[len('<yang>'):] |
| 42 | if voltha_xml_string.endswith('</yang>'): |
| 43 | voltha_xml_string = voltha_xml_string[:-len('</yang>')] |
Khen Nursimulu | c7991dd | 2017-01-05 17:05:48 -0500 | [diff] [blame] | 44 | # Empty response |
| 45 | elif voltha_xml_string.startswith('<yang/>'): |
| 46 | voltha_xml_string='' |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 47 | |
| 48 | # Create the xml body as |
| 49 | if request.has_key('subclass'): |
| 50 | body = ''.join([ |
| 51 | '<data>', |
| 52 | '<', |
| 53 | request['class'], |
Khen Nursimulu | c9ef7c1 | 2017-01-04 20:40:53 -0500 | [diff] [blame] | 54 | ' xmlns="', |
| 55 | request['namespace'], |
| 56 | '">', |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 57 | '<', |
| 58 | request['subclass'], |
| 59 | '>', |
| 60 | voltha_xml_string, |
| 61 | '</', |
| 62 | request['subclass'], |
| 63 | '>', |
| 64 | '</', |
| 65 | request['class'], |
| 66 | '>', |
| 67 | '</data>' |
| 68 | ]) |
| 69 | else: |
| 70 | body = ''.join([ |
| 71 | '<data>', |
| 72 | '<', |
| 73 | request['class'], |
| 74 | ' xmlns="urn:opencord:params:xml:ns:voltha:ietf-voltha">', |
| 75 | voltha_xml_string, |
| 76 | '</', |
| 77 | request['class'], |
| 78 | '>', |
| 79 | '</data>' |
| 80 | ]) |
| 81 | |
| 82 | return etree.fromstring(body) |
| 83 | |
| 84 | def add_node(self, new_node, tree): |
| 85 | if new_node.tag == 'ignore': |
| 86 | # We want only sub-elements |
| 87 | for elem in list(new_node): |
| 88 | tree.append(elem) |
| 89 | else: |
| 90 | tree.append(new_node) |
| 91 | |
| 92 | def copy_basic_element(self, elm): |
| 93 | new_elem = etree.Element(elm.tag) |
| 94 | new_elem.text = elm.text |
| 95 | return new_elem |
| 96 | |
| 97 | def process_inline_option(self, elem): |
| 98 | inline_option = False |
| 99 | inline_node_name = None |
| 100 | for elm in list(elem): |
| 101 | if elm.tag == 'yang_field_option': |
| 102 | inline_option = True |
| 103 | if elm.tag == 'name': |
| 104 | inline_node_name = elm.text |
| 105 | if not inline_option: |
| 106 | new_elem = etree.Element(elem.tag) |
| 107 | return new_elem, elem |
| 108 | |
| 109 | # look for the node with the inline_node_name |
| 110 | for elm in list(elem): |
| 111 | if elm.tag == inline_node_name: |
| 112 | new_elem = etree.Element('ignore') |
| 113 | return new_elem, elm |
| 114 | |
| 115 | def process_element(self, elem): |
| 116 | attrib = elem.get('type') |
| 117 | if (attrib == 'list'): |
| 118 | if list(elem) is None: |
| 119 | return self.copy_basic_element(elem) |
| 120 | new_elem = etree.Element('ignore') |
| 121 | for elm in list(elem): |
| 122 | elm.tag = elem.tag |
| 123 | if elm.get('type') in ['list', 'dict']: |
| 124 | self.add_node(self.process_element(elm), new_elem) |
| 125 | else: |
| 126 | new_elem.append(self.copy_basic_element(elm)) |
| 127 | return new_elem |
| 128 | elif (attrib == 'dict'): |
| 129 | # Empty case |
| 130 | if list(elem) is None: |
| 131 | return self.copy_basic_element(elem) |
| 132 | |
| 133 | # Process field option. |
| 134 | new_elem, elem = self.process_inline_option(elem) |
| 135 | |
| 136 | for elm in list(elem): |
| 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 | else: |
| 143 | return self.copy_basic_element(elem) |
| 144 | |
| 145 | def to_yang_xml(self, from_xml): |
| 146 | # Parse from_xml as follows: |
| 147 | # 1. Any element having a list attribute shoud have each item move 1 level |
| 148 | # up and retag using the parent tag |
| 149 | # 2. Any element having a dict attribute and has a <yang_field_option> |
| 150 | # sub-element should have all it's items move to teh parent level |
| 151 | top = etree.Element('yang') |
| 152 | elms = list(from_xml) |
| 153 | |
| 154 | # special case the xml contain a list type |
| 155 | if len(elms) == 1: |
| 156 | item = elms[0] |
Khen Nursimulu | c7991dd | 2017-01-05 17:05:48 -0500 | [diff] [blame] | 157 | #TODO: Address the case where the content is a list of list |
Khen Nursimulu | a497274 | 2016-12-23 17:15:20 -0500 | [diff] [blame] | 158 | if item.get('type') == 'list': |
| 159 | item.tag = 'ignore' |
| 160 | self.add_node(self.process_element(item), top) |
| 161 | return top |
| 162 | |
| 163 | # Process normally for all other cases |
| 164 | for elm in elms: |
| 165 | self.add_node(self.process_element(elm), top) |
| 166 | |
| 167 | return top |
| 168 | |
| 169 | def build_yang_response(self, root, request): |
| 170 | try: |
| 171 | yang_xml = self.to_yang_xml(root) |
| 172 | log.info('yang-xml', yang_xml=etree.tounicode(yang_xml, |
| 173 | pretty_print=True)) |
| 174 | return self.build_xml_response(request, yang_xml) |
| 175 | except Exception as e: |
| 176 | self.rpc_response.is_error = True |
| 177 | self.rpc_response.node = ncerror.BadMsg(request) |
| 178 | return |