2 Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 This program and the accompanying materials are made available under the
5 terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 and is available at http://www.eclipse.org/legal/epl-v10.html
8 Created on May 11, 2014
10 @author: <a href="mailto:vdemcak@cisco.com">Vaclav Demcak</a>
13 from xml.dom.minidom import Element
15 from openvswitch.parser_tools import ParseTools
16 from tools.crud_test_with_param_superclass import OF_CRUD_Test_Base
17 from tools.xml_parser_tools import XMLtoDictParserTools
18 import xml.dom.minidom as md
21 METER_ID_TAG_NAME = 'meter-id'
22 # TODO : find why band-burst-size has same value as dscp-remark (same for band-rate)
23 IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON = ['meter-name', 'container-name', 'band-burst-size', 'band-rate', 'flags', 'perc_level', 'barrier']
25 METER_TAGS_FOR_UPDATE = ['band-burst-size', 'band-rate']
27 class OF_CRUD_Test_Meters( OF_CRUD_Test_Base ):
31 super( OF_CRUD_Test_Meters, self ).setUp()
33 ids = ParseTools.get_values( self.xml_input_DOM, METER_ID_TAG_NAME )
34 data = ( self.host, self.port, ids[METER_ID_TAG_NAME] )
35 self.conf_url = 'http://%s:%d/restconf/config/opendaylight-inventory:nodes' \
36 '/node/openflow:1/meter/%s' % data
37 self.oper_url = 'http://%s:%d/restconf/operational/opendaylight-inventory:nodes' \
38 '/node/openflow:1/meter/%s' % data
40 data = ( self.host, self.port )
41 self.conf_url_post = 'http://%s:%d/restconf/config/opendaylight-inventory:nodes' \
42 '/node/openflow:1/' % data
43 # ----- SAL SERVICE OPERATIONS -----
44 self.oper_url_add = 'http://%s:%d/restconf/operations/sal-meter:add-meter' % data
45 self.oper_url_upd = 'http://%s:%d/restconf/operations/sal-meter:update-meter' % data
46 self.oper_url_del = 'http://%s:%d/restconf/operations/sal-meter:remove-meter' % data
48 self.data_from_file_input = ''
49 for node in self.xml_input_DOM.documentElement.childNodes :
50 self.data_from_file_input += node.toxml( encoding = 'utf-8' )
52 # The xml body without data - data come from file (all meter subtags)
53 self.oper_input_stream = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n' \
54 '<input xmlns="urn:opendaylight:meter:service">\n' \
56 '<node xmlns:inv="urn:opendaylight:inventory">/inv:nodes/inv:node[inv:id="openflow:1"]</node>\n' \
57 '</input>' % self.data_from_file_input
60 def tearDown( self ) :
61 # cleaning configuration DataStore without a response validation
62 self.log.info( self._paint_msg_cyan( 'Uncontrolled cleaning after meter test' ) )
63 requests.delete( self.conf_url, auth = self._get_auth(),
64 headers = self._get_xml_result_header() )
65 # cleaning device without a response validation
66 requests.post( self.oper_url_del, data = self.oper_input_stream, auth = self._get_auth(),
67 headers = self._get_xml_request_result_header() )
68 super( OF_CRUD_Test_Meters, self ).tearDown()
71 def test_conf_PUT( self ):
72 self.log.info( "--- Meter conf. PUT test ---" )
73 # -------------- CREATE -------------------
74 self.log.info( self._paint_msg_yellow( " CREATE Meter by PUT REST" ) )
75 # send request via RESTCONF
76 self.put_REST_XML_conf_request( self.conf_url, self.xml_input_stream )
77 # check request content against restconf's config datastore
78 response = self.get_REST_XML_response( self.conf_url )
79 xml_resp_stream = ( response.text ).encode( 'utf-8', 'ignore' )
80 xml_resp_DOM = md.parseString( xml_resp_stream )
81 self.assertDataDOM( self.xml_input_DOM, xml_resp_DOM )
82 # check request content against restconf's operational datastore
83 response = self.get_REST_XML_response( self.oper_url )
84 self.__validate_contain_meter( response, self.xml_input_DOM )
86 # -------------- UPDATE -------------------
87 self.log.info( self._paint_msg_yellow( " UPDATE Meter by PUT REST" ) )
88 xml_updated_stream = self.__update_meter_input()
89 self.put_REST_XML_conf_request( self.conf_url, xml_updated_stream )
90 # check request content against restconf's config datastore
91 response = self.get_REST_XML_response( self.conf_url )
92 xml_resp_stream = ( response.text ).encode( 'utf-8', 'ignore' )
93 xml_resp_DOM = md.parseString( xml_resp_stream )
94 xml_upd_DOM = md.parseString( xml_updated_stream )
95 self.assertDataDOM( xml_upd_DOM, xml_resp_DOM )
96 # check request content against restconf's operational datastore
97 response = self.get_REST_XML_response( self.oper_url )
98 self.__validate_contain_meter( response, xml_upd_DOM )
100 # -------------- DELETE -------------------
101 self.log.info( self._paint_msg_yellow( " DELETE Meter by DELETE REST " ) )
102 # Delte data from config DataStore
103 response = self.delete_REST_XML_response( self.conf_url )
104 # Data has been deleted, so we expect the 404 response code
105 response = self.get_REST_XML_deleted_response( self.conf_url )
106 # Meter operational data has a specific content
107 # and the comparable data has to be filtered before comparison
108 response = self.get_REST_XML_response( self.oper_url )
109 self.__validate_contain_meter( response, self.xml_input_DOM, False )
112 def test_conf_POST( self ):
113 self.log.info( "--- Meter conf. POST test ---" )
114 # -------------- CREATE -------------------
115 self.log.info( self._paint_msg_yellow( " CREATE Meter by POST REST" ) )
116 # send request via RESTCONF
117 self.post_REST_XML_request( self.conf_url_post, self.xml_input_stream )
118 # check request content against restconf's config datastore
119 response = self.get_REST_XML_response( self.conf_url )
120 xml_resp_stream = ( response.text ).encode( 'utf-8', 'ignore' )
121 xml_resp_DOM = md.parseString( xml_resp_stream )
122 self.assertDataDOM( self.xml_input_DOM, xml_resp_DOM )
123 # check request content against restconf's operational datastore
124 response = self.get_REST_XML_response( self.oper_url )
125 self.__validate_contain_meter( response, self.xml_input_DOM )
126 # test error for double create (POST could create data only)
127 self.log.info( self._paint_msg_yellow( " UPDATE Meter by POST REST" ) )
128 response = self.post_REST_XML_repeat_request( self.conf_url_post, self.xml_input_stream )
130 # -------------- DELETE -------------------
131 self.log.info( self._paint_msg_yellow( " DELETE Meter by DELETE REST" ) )
132 # Delte data from config DataStore
133 response = self.delete_REST_XML_response( self.conf_url )
134 # Data has been deleted, so we expect the 404 response code
135 response = self.get_REST_XML_deleted_response( self.conf_url )
136 # Meter operational data has a specific content
137 # and the comparable data has to be filtered before comparison
138 response = self.get_REST_XML_response( self.oper_url )
139 self.__validate_contain_meter( response, self.xml_input_DOM, False )
143 def test_operations_POST( self ):
144 self.log.info( "--- Meter operations sal-service test ---" )
145 # -------------- CREATE -------------------
146 self.log.info( self._paint_msg_yellow( " CREATE Meter by add-sal-service" ) )
147 # send request via RESTCONF
148 self.post_REST_XML_request( self.oper_url_add, self.oper_input_stream )
149 # TODO : check no empty transaction_id from post add_service
151 # check request content against restconf's config datastore
152 # operation service don't change anything in a Config. Data Store
153 # so we expect 404 response code (same as a check after delete
154 self.get_REST_XML_deleted_response( self.conf_url )
155 # check request content against restconf's operational datastore
156 # operational Data Store has to present new meter
157 response = self.get_REST_XML_response( self.oper_url )
158 self.__validate_contain_meter( response, self.xml_input_DOM )
160 # -------------- UPDATE -------------------
161 self.log.info( self._paint_msg_yellow( " UPDATE Meter by update-sal-service" ) )
162 xml_updated_stream = self.__update_meter_input();
163 xml_updated_DOM = md.parseString( xml_updated_stream )
164 data_from_updated_stream = ''
165 for node in xml_updated_DOM.documentElement.childNodes :
166 data_from_updated_stream += node.toxml( encoding = 'utf-8' )
168 # The xml body without data - data come from file (all meters's subtags)
169 oper_update_stream = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n' \
170 '<input xmlns="urn:opendaylight:meter:service">\n' \
171 '<original-meter>\n' \
173 '</original-meter>\n' \
176 '</updated-meter>\n'\
177 '<node xmlns:inv="urn:opendaylight:inventory">/inv:nodes/inv:node[inv:id="openflow:1"]</node>\n' \
178 '</input>' % ( self.data_from_file_input, data_from_updated_stream )
180 self.post_REST_XML_request( self.oper_url_upd, oper_update_stream )
181 # TODO : check no empty transaction_id from post add_service
183 # check request content against restconf's config datastore
184 # operation service don't change anything in a Config. Data Store
185 # so we expect 404 response code (same as a check after delete
186 self.get_REST_XML_deleted_response( self.conf_url )
187 # check request content against restconf's operational datastore
188 # operational Data Store has to present updated meter
189 response = self.get_REST_XML_response( self.oper_url )
190 self.__validate_contain_meter( response, xml_updated_DOM )
192 # -------------- DELETE -------------------
193 self.log.info( self._paint_msg_yellow( " DELETE Meter by remove-sal-service" ) )
194 # Delte data from config DataStore
195 response = self.post_REST_XML_request( self.oper_url_del, self.oper_input_stream )
196 # Data never been added, so we expect the 404 response code
197 response = self.get_REST_XML_deleted_response( self.conf_url )
198 # Meter operational data has a specific content
199 # and the comparable data has to be filtered before comparison
200 response = self.get_REST_XML_response( self.oper_url )
201 self.__validate_contain_meter( response, self.xml_input_DOM, False )
204 # --------------- HELP METHODS ---------------
207 def __validate_contain_meter( self, oper_resp, orig_DOM, exp_contain = True ):
208 xml_resp_stream = ( oper_resp.text ).encode( 'utf-8', 'ignore' )
209 xml_resp_DOM = md.parseString( xml_resp_stream )
210 nodeListOperMeters = xml_resp_DOM.getElementsByTagName( 'meter-config-stats' )
211 if ( nodeListOperMeters.length > 1 ) :
212 raise AssertionError( '\n !!! Operational Data Store has more \'meter-config-stats\' tags as one \n' )
214 origDict = XMLtoDictParserTools.parseDOM_ToDict( orig_DOM._get_documentElement(),
215 ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
216 origDict['meter-config-stats'] = origDict.pop( 'meter' )
218 for node in nodeListOperMeters :
219 nodeDict = XMLtoDictParserTools.parseDOM_ToDict( node,
220 ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
223 if nodeDict != origDict :
224 err_msg = '\n !!! Loaded operation statistics doesn\'t contain expected meter \n' \
225 ' expected: %s\n found: %s\n differences: %s\n' \
226 '' % ( origDict, nodeDict, XMLtoDictParserTools.getDifferenceDict( origDict, nodeDict ) )
227 self.log.error( self._paint_msg_red( err_msg ) )
228 raise AssertionError( err_msg )
230 if nodeDict == origDict :
231 err_msg = '\n !!! Loaded operation statistics contains expected meter \n' \
232 ' found: %s\n ' % ( nodeDict )
233 self.log.error( self._paint_msg_red( err_msg ) )
234 raise AssertionError( err_msg )
237 def __update_meter_input( self ):
238 # action only for yet
239 xml_dom_input = md.parseString( self.xml_input_stream )
240 for tag_name in METER_TAGS_FOR_UPDATE :
241 tag_list = xml_dom_input.getElementsByTagName( tag_name )
242 if tag_list is not None and len( tag_list ) > 0 :
243 tag_elm = tag_list[0]
244 for child in tag_elm.childNodes :
245 if child.nodeType == Element.TEXT_NODE :
246 nodeValue = ( child.nodeValue ).encode( 'utf-8', 'ignore' )
247 if ( len( nodeValue.strip( ' \t\n\r' ) ) ) > 0 :
248 newValue = self.returnReverseInputTest( nodeValue )
249 newTagEl = child.ownerDocument.createTextNode( newValue )
250 self.log.info( self._paint_msg_cyan( 'Meter change for %s from %s to %s' % ( tag_name, nodeValue, newValue ) ) )
251 child.parentNode.replaceChild( newTagEl, child )
253 return xml_dom_input.toxml( encoding = 'utf-8' )