CRUD ODL python RESTconf tests
[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         data_from_file_input = ''
49         for node in self.xml_input_DOM.documentElement.childNodes :
50             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:meter:service">\n' \
55                                         '%s' \
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' )
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
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 self.xml_input_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:meter:service">\n' \
185                                     '%s' \
186                                     '<node xmlns:inv="urn:opendaylight:inventory">/inv:nodes/inv:node[inv:id="openflow:1"]</node>\n' \
187                                 '</input>' % data_from_updated_stream
188
189         self.post_REST_XML_request( self.oper_url_upd, oper_update_stream )
190         # TODO : check no empty transaction_id from post add_service
191
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 )
200
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 )
211
212
213 # --------------- HELP METHODS ---------------
214
215
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 )
224
225         origDict = XMLtoDictParserTools.parseDOM_ToDict( orig_DOM._get_documentElement(),
226                                                         ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
227         origDict['group-desc'] = origDict.pop( 'group' )
228         nodeDict = {}
229         for node in nodeListOperGroup :
230             nodeDict = XMLtoDictParserTools.parseDOM_ToDict( node,
231                                                             ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
232         if exp_contain :
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 )
239
240         else :
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 )
246
247
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 )
262
263         return xml_dom_input.toxml( encoding = 'utf-8' )