BUG 2661 - sonar issues in statistics-manager
[openflowplugin.git] / test-scripts / crud / odl_meter_test.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 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']
24
25 METER_TAGS_FOR_UPDATE = ['band-burst-size', 'band-rate']
26
27 class OF_CRUD_Test_Meters( OF_CRUD_Test_Base ):
28
29
30     def setUp( self ):
31         super( OF_CRUD_Test_Meters, self ).setUp()
32         # ----- PUT -----
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
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-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
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:meter: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
59
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()
69
70
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 )
85
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 )
99
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 )
110
111
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 )
129
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 )
140
141
142     # sal-meter services
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
150
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 )
159
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' )
167
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' \
172                                         '%s' \
173                                     '</original-meter>\n' \
174                                     '<updated-meter>\n'\
175                                         '%s'\
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 )
179
180         self.post_REST_XML_request( self.oper_url_upd, oper_update_stream )
181         # TODO : check no empty transaction_id from post add_service
182
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 )
191
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 )
202
203
204 # --------------- HELP METHODS ---------------
205
206
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' )
213
214         origDict = XMLtoDictParserTools.parseDOM_ToDict( orig_DOM._get_documentElement(),
215                                                         ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
216         origDict['meter-config-stats'] = origDict.pop( 'meter' )
217         nodeDict = {}
218         for node in nodeListOperMeters :
219             nodeDict = XMLtoDictParserTools.parseDOM_ToDict( node,
220                                                             ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
221
222         if exp_contain :
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 )
229         else :
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 )
235
236
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 )
252
253         return xml_dom_input.toxml( encoding = 'utf-8' )