Bug 6745 Remove thread renaming and unnecessary logging
[openflowplugin.git] / test-scripts / crud / odl_group_tests.py
1 '''
2 Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3
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
7
8 Created on May 11, 2014
9
10 @author: <a href="mailto:vdemcak@cisco.com">Vaclav Demcak</a>
11 '''
12 import requests
13 from xml.dom.minidom import Element
14
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
19
20
21 GROUP_ID_TAG_NAME = 'group-id'
22 # TODO : fix groups ignored tags
23 IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON = ['group-name', 'barrier', 'weight']
24
25 METER_TAGS_FOR_UPDATE = ['watch_group', 'watch_port']
26
27 class OF_CRUD_Test_Groups( OF_CRUD_Test_Base ):
28
29
30     def setUp( self ):
31         super( OF_CRUD_Test_Groups, self ).setUp()
32         # ----- PUT -----
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
39         # ----- POST -----
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
47         # Modify input 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' )
51
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:group:service">\n' \
55                                         '%s' \
56                                         '<node xmlns:inv="urn:opendaylight:inventory">/inv:nodes/inv:node[inv:id="openflow:1"]</node>\n' \
57                                     '</input>' % self.data_from_file_input
58         # Modify input data for delete
59         data_from_file_input_del = ''
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_del += node.toxml( encoding = 'utf-8' )
64
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' \
68                                             '%s' \
69                                             '<node xmlns:inv="urn:opendaylight:inventory">/inv:nodes/inv:node[inv:id="openflow:1"]</node>\n' \
70                                         '</input>' % data_from_file_input_del
71
72
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()
81
82
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 )
97
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 )
111
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 )
123
124
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 )
142
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 )
154
155
156     # sal-group services
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
164
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 )
173
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 xml_updated_DOM.documentElement.childNodes :
180             data_from_updated_stream += node.toxml( encoding = 'utf-8' )
181
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:group:service">\n' \
185                                     '<original-group>\n'\
186                                         '%s' \
187                                     '</original-group>\n'\
188                                     '<updated-group>\n'\
189                                         '%s' \
190                                     '</updated-group>\n'\
191                                     '<node xmlns:inv="urn:opendaylight:inventory">/inv:nodes/inv:node[inv:id="openflow:1"]</node>\n' \
192                                 '</input>' % ( self.data_from_file_input, data_from_updated_stream )
193
194         self.post_REST_XML_request( self.oper_url_upd, oper_update_stream )
195         # TODO : check no empty transaction_id from post add_service
196
197         # check request content against restconf's config datastore
198         # operation service don't change anything in a Config. Data Store
199         # so we expect 404 response code (same as a check after delete
200         self.get_REST_XML_deleted_response( self.conf_url )
201         # check request content against restconf's operational datastore
202         # operational Data Store has to present updated group
203         response = self.get_REST_XML_response( self.oper_url )
204         self.__validate_contain_group( response, xml_updated_DOM )
205
206         # -------------- DELETE -------------------
207         self.log.info( self._paint_msg_yellow( " DELETE Group by remove-sal-service" ) )
208         # Delte data from config DataStore
209         response = self.post_REST_XML_request( self.oper_delete_url, self.oper_delete_input_stream )
210         # Data never been added, so we expect the 404 response code
211         response = self.get_REST_XML_deleted_response( self.conf_url )
212         # Group operational data has a specific content
213         # and the comparable data has to be filtered before comparison
214         response = self.get_REST_XML_response( self.oper_url )
215         self.__validate_contain_group( response, self.xml_input_DOM, False )
216
217
218 # --------------- HELP METHODS ---------------
219
220
221     def __validate_contain_group( self, oper_resp, orig_DOM, exp_contain = True ):
222         xml_resp_stream = ( oper_resp.text ).encode( 'utf-8', 'ignore' )
223         xml_resp_DOM = md.parseString( xml_resp_stream )
224         nodeListOperGroup = xml_resp_DOM.getElementsByTagName( 'group-desc' )
225         if ( nodeListOperGroup.length > 1 ) :
226             err_msg = '\n !!! Operational Data Store has more groups as one \n'
227             self.log.error( self._paint_msg_red( err_msg ) )
228             raise AssertionError( err_msg )
229
230         origDict = XMLtoDictParserTools.parseDOM_ToDict( orig_DOM._get_documentElement(),
231                                                         ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
232         origDict['group-desc'] = origDict.pop( 'group' )
233         nodeDict = {}
234         for node in nodeListOperGroup :
235             nodeDict = XMLtoDictParserTools.parseDOM_ToDict( node,
236                                                             ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
237         if exp_contain :
238             if nodeDict != origDict :
239                 err_msg = '\n !!! Loaded operation statistics doesn\'t contain expected group \n' \
240                             ' expected:      %s\n found:         %s\n differences:   %s\n' \
241                             '' % ( origDict, nodeDict, XMLtoDictParserTools.getDifferenceDict( origDict, nodeDict ) )
242                 self.log.error( self._paint_msg_red( err_msg ) )
243                 raise AssertionError( err_msg )
244
245         else :
246             if nodeDict == origDict :
247                 err_msg = '\n !!! Loaded operation statistics contains expected group, delete fail \n' \
248                             ' found:         %s\n' % ( nodeDict )
249                 self.log.error( self._paint_msg_red( err_msg ) )
250                 raise AssertionError( err_msg )
251
252
253     def __update_group_input( self ):
254         # action only for yet
255         xml_dom_input = md.parseString( self.xml_input_stream )
256         for tag_name in METER_TAGS_FOR_UPDATE :
257             tag_list = xml_dom_input.getElementsByTagName( tag_name )
258             if tag_list is not None and len( tag_list ) > 0 :
259                 tag_elm = tag_list[0]
260                 for child in tag_elm.childNodes :
261                     if child.nodeType == Element.TEXT_NODE :
262                         nodeValue = ( child.nodeValue ).encode( 'utf-8', 'ignore' )
263                         if ( len( nodeValue.strip( ' \t\n\r' ) ) ) > 0 :
264                             newValue = self.returnReverseInputTest( nodeValue )
265                             newTagEl = child.ownerDocument.createTextNode( newValue )
266                             child.parentNode.replaceChild( newTagEl, child )
267
268         return xml_dom_input.toxml( encoding = 'utf-8' )