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 GROUP_ID_TAG_NAME = 'group-id'
22 # TODO : fix groups ignored tags
23 IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON = ['group-name', 'barrier', 'weight']
25 METER_TAGS_FOR_UPDATE = ['watch_group', 'watch_port']
27 class OF_CRUD_Test_Groups( OF_CRUD_Test_Base ):
31 super( OF_CRUD_Test_Groups, self ).setUp()
33 ids = ParseTools.get_values( self.xml_input_DOM, GROUP_ID_TAG_NAME )
34 data = ( self.host, self.port, ids[GROUP_ID_TAG_NAME] )
35 self.conf_url = 'http://%s:%d/restconf/config/opendaylight-inventory:nodes' \
36 '/node/openflow:1/group/%s' % data
37 self.oper_url = 'http://%s:%d/restconf/operational/opendaylight-inventory:nodes' \
38 '/node/openflow:1/group/%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-group:add-group' % data
45 self.oper_url_upd = 'http://%s:%d/restconf/operations/sal-group:update-group' % data
46 self.oper_delete_url = 'http://%s:%d/restconf/operations/sal-group:remove-group' % data
48 data_from_file_input = ''
49 for node in self.xml_input_DOM.documentElement.childNodes :
50 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>' % data_from_file_input
58 # Modify input data for delete
59 data_from_file_input = ''
60 for node in self.xml_input_DOM.documentElement.childNodes :
61 nodeKey = None if node.localName == None else ( node.localName ).encode( 'utf-8', 'ignore' )
62 if ( nodeKey is None or nodeKey != 'buckets' ) :
63 data_from_file_input += node.toxml( encoding = 'utf-8' )
65 # The xml body without data - data come from file (all group subtags)
66 self.oper_delete_input_stream = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n' \
67 '<input xmlns="urn:opendaylight:group:service">\n' \
69 '<node xmlns:inv="urn:opendaylight:inventory">/inv:nodes/inv:node[inv:id="openflow:1"]</node>\n' \
70 '</input>' % data_from_file_input
73 def tearDown( self ) :
74 # cleaning configuration DataStore and device without a response validation
75 self.log.info( self._paint_msg_cyan( 'Uncontrolled cleaning after group test' ) )
76 requests.delete( self.conf_url, auth = self._get_auth(),
77 headers = self._get_xml_result_header() )
78 requests.post( self.oper_delete_url, data = self.oper_delete_input_stream,
79 auth = self._get_auth(), headers = self._get_xml_request_result_header() )
80 super( OF_CRUD_Test_Groups, self ).tearDown()
83 def test_conf_PUT( self ):
84 self.log.info( "--- Group conf. PUT test ---" )
85 # -------------- CREATE -------------------
86 self.log.info( self._paint_msg_yellow( " CREATE Group by PUT REST" ) )
87 # send request via RESTCONF
88 self.put_REST_XML_conf_request( self.conf_url, self.xml_input_stream )
89 # check request content against restconf's config datastore
90 response = self.get_REST_XML_response( self.conf_url )
91 xml_resp_stream = ( response.text ).encode( 'utf-8', 'ignore' )
92 xml_resp_DOM = md.parseString( xml_resp_stream )
93 self.assertDataDOM( self.xml_input_DOM, xml_resp_DOM )
94 # check request content against restconf's operational datastore
95 response = self.get_REST_XML_response( self.oper_url )
96 self.__validate_contain_group( response, self.xml_input_DOM )
98 # -------------- UPDATE -------------------
99 self.log.info( self._paint_msg_yellow( " UPDATE Group by PUT REST" ) )
100 xml_updated_stream = self.__update_group_input();
101 self.put_REST_XML_conf_request( self.conf_url, xml_updated_stream )
102 # check request content against restconf's config datastore
103 response = self.get_REST_XML_response( self.conf_url )
104 xml_resp_stream = ( response.text ).encode( 'utf-8', 'ignore' )
105 xml_resp_DOM = md.parseString( xml_resp_stream )
106 xml_upd_DOM = md.parseString( xml_updated_stream )
107 self.assertDataDOM( xml_upd_DOM, xml_resp_DOM )
108 # check request content against restconf's operational datastore
109 response = self.get_REST_XML_response( self.oper_url )
110 self.__validate_contain_group( response, xml_upd_DOM )
112 # -------------- DELETE -------------------
113 self.log.info( self._paint_msg_yellow( " DELETE Group by DELETE REST and sal-remove service " ) )
114 # Delte data from config DataStore
115 response = self.post_REST_XML_request( self.oper_delete_url, self.oper_delete_input_stream )
116 response = self.delete_REST_XML_response( self.conf_url )
117 # Data has been deleted, so we expect the 404 response code
118 response = self.get_REST_XML_deleted_response( self.conf_url )
119 # Group operational data has a specific content
120 # and the comparable data has to be filtered before comparison
121 response = self.get_REST_XML_response( self.oper_url )
122 self.__validate_contain_group( response, self.xml_input_DOM, False )
125 def test_conf_POST( self ):
126 self.log.info( "--- Group conf. POST test ---" )
127 # -------------- CREATE -------------------
128 self.log.info( self._paint_msg_yellow( " CREATE Group by POST REST" ) )
129 # send request via RESTCONF
130 self.post_REST_XML_request( self.conf_url_post, self.xml_input_stream )
131 # check request content against restconf's config datastore
132 response = self.get_REST_XML_response( self.conf_url )
133 xml_resp_stream = ( response.text ).encode( 'utf-8', 'ignore' )
134 xml_resp_DOM = md.parseString( xml_resp_stream )
135 self.assertDataDOM( self.xml_input_DOM, xml_resp_DOM )
136 # check request content against restconf's operational datastore
137 response = self.get_REST_XML_response( self.oper_url )
138 self.__validate_contain_group( response, self.xml_input_DOM )
139 # test error for double create (POST could create data only)
140 self.log.info( self._paint_msg_yellow( " UPDATE Group by POST REST" ) )
141 response = self.post_REST_XML_repeat_request( self.conf_url_post, self.xml_input_stream )
143 # -------------- DELETE -------------------
144 self.log.info( self._paint_msg_yellow( " DELETE Group by DELETE REST" ) )
145 # Delte data from config DataStore
146 response = self.post_REST_XML_request( self.oper_delete_url, self.oper_delete_input_stream )
147 response = self.delete_REST_XML_response( self.conf_url )
148 # Data has been deleted, so we expect the 404 response code
149 response = self.get_REST_XML_deleted_response( self.conf_url )
150 # Group operational data has a specific content
151 # and the comparable data has to be filtered before comparison
152 response = self.get_REST_XML_response( self.oper_url_get )
153 self.__validate_contain_group( response, self.xml_input_DOM, False )
157 def test_operations_POST( self ):
158 self.log.info( "--- Group operations sal-service test ---" )
159 # -------------- CREATE -------------------
160 self.log.info( self._paint_msg_yellow( " CREATE Group by add-sal-service" ) )
161 # send request via RESTCONF
162 self.post_REST_XML_request( self.oper_url_add, self.oper_input_stream )
163 # TODO : check no empty transaction_id from post add_service
165 # check request content against restconf's config datastore
166 # operation service don't change anything in a Config. Data Store
167 # so we expect 404 response code (same as a check after delete
168 self.get_REST_XML_deleted_response( self.conf_url )
169 # check request content against restconf's operational datastore
170 # operational Data Store has to present new group
171 response = self.get_REST_XML_response( self.oper_url )
172 self.__validate_contain_group( response, self.xml_input_DOM )
174 # -------------- UPDATE -------------------
175 self.log.info( self._paint_msg_yellow( " UPDATE Group by update-sal-service" ) )
176 xml_updated_stream = self.__update_group_input();
177 xml_updated_DOM = md.parseString( xml_updated_stream )
178 data_from_updated_stream = ''
179 for node in self.xml_input_DOM.documentElement.childNodes :
180 data_from_updated_stream += node.toxml( encoding = 'utf-8' )
182 # The xml body without data - data come from file (all groups's subtags)
183 oper_update_stream = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n' \
184 '<input xmlns="urn:opendaylight:meter:service">\n' \
186 '<node xmlns:inv="urn:opendaylight:inventory">/inv:nodes/inv:node[inv:id="openflow:1"]</node>\n' \
187 '</input>' % data_from_updated_stream
189 self.post_REST_XML_request( self.oper_url_upd, oper_update_stream )
190 # TODO : check no empty transaction_id from post add_service
192 # check request content against restconf's config datastore
193 # operation service don't change anything in a Config. Data Store
194 # so we expect 404 response code (same as a check after delete
195 self.get_REST_XML_deleted_response( self.conf_url )
196 # check request content against restconf's operational datastore
197 # operational Data Store has to present updated group
198 response = self.get_REST_XML_response( self.oper_url )
199 self.__validate_contain_group( response, xml_updated_DOM )
201 # -------------- DELETE -------------------
202 self.log.info( self._paint_msg_yellow( " DELETE Group by remove-sal-service" ) )
203 # Delte data from config DataStore
204 response = self.post_REST_XML_request( self.oper_delete_url, self.__get_oper_delete_input_stream() )
205 # Data never been added, so we expect the 404 response code
206 response = self.get_REST_XML_deleted_response( self.conf_url )
207 # Group operational data has a specific content
208 # and the comparable data has to be filtered before comparison
209 response = self.get_REST_XML_response( self.oper_url )
210 self.__validate_contain_group( response, self.xml_input_DOM, False )
213 # --------------- HELP METHODS ---------------
216 def __validate_contain_group( self, oper_resp, orig_DOM, exp_contain = True ):
217 xml_resp_stream = ( oper_resp.text ).encode( 'utf-8', 'ignore' )
218 xml_resp_DOM = md.parseString( xml_resp_stream )
219 nodeListOperGroup = xml_resp_DOM.getElementsByTagName( 'group-desc' )
220 if ( nodeListOperGroup.length > 1 ) :
221 err_msg = '\n !!! Operational Data Store has more groups as one \n'
222 self.log.error( self._paint_msg_red( err_msg ) )
223 raise AssertionError( err_msg )
225 origDict = XMLtoDictParserTools.parseDOM_ToDict( orig_DOM._get_documentElement(),
226 ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
227 origDict['group-desc'] = origDict.pop( 'group' )
229 for node in nodeListOperGroup :
230 nodeDict = XMLtoDictParserTools.parseDOM_ToDict( node,
231 ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
233 if nodeDict != origDict :
234 err_msg = '\n !!! Loaded operation statistics doesn\'t contain expected group \n' \
235 ' expected: %s\n found: %s\n differences: %s\n' \
236 '' % ( origDict, nodeDict, XMLtoDictParserTools.getDifferenceDict( origDict, nodeDict ) )
237 self.log.error( self._paint_msg_red( err_msg ) )
238 raise AssertionError( err_msg )
241 if nodeDict == origDict :
242 err_msg = '\n !!! Loaded operation statistics contains expected group, delete fail \n' \
243 ' found: %s\n' % ( nodeDict )
244 self.log.error( self._paint_msg_red( err_msg ) )
245 raise AssertionError( err_msg )
248 def __update_group_input( self ):
249 # action only for yet
250 xml_dom_input = md.parseString( self.xml_input_stream )
251 for tag_name in METER_TAGS_FOR_UPDATE :
252 tag_list = xml_dom_input.getElementsByTagName( tag_name )
253 if tag_list is not None and len( tag_list ) > 0 :
254 tag_elm = tag_list[0]
255 for child in tag_elm.childNodes :
256 if child.nodeType == Element.TEXT_NODE :
257 nodeValue = ( child.nodeValue ).encode( 'utf-8', 'ignore' )
258 if ( len( nodeValue.strip( ' \t\n\r' ) ) ) > 0 :
259 newValue = self.returnReverseInputTest( nodeValue )
260 newTagEl = child.ownerDocument.createTextNode( newValue )
261 child.parentNode.replaceChild( newTagEl, child )
263 return xml_dom_input.toxml( encoding = 'utf-8' )