Coverage - ancestor mocking class for services testing.
[openflowplugin.git] / test-scripts / tools / crud_test_with_param_superclass.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 18, 2014
9
10 @author: <a href="mailto:vdemcak@cisco.com">Vaclav Demcak</a>
11 '''
12 import logging
13 import requests
14 import sys
15 import time
16 import unittest
17
18 from tools.file_loader_tool import FileLoaderTools
19 from tools.xml_parser_tools import XMLtoDictParserTools
20 import xml.dom.minidom as md
21
22
23 class ColorEnum ( object ):
24     '''
25     Color Enum class for coloring log output
26     '''
27     BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range( 8 )
28
29 class OF_CRUD_Test_Base( unittest.TestCase ):
30
31
32     def __init__( self, methodName = 'testCRUD', path_to_xml = None ):
33         """
34         private constructor
35             * logger initialization (Child Class Name + xml input file nr.
36             * add a path for the xml input file to the local variable
37         """
38         super( OF_CRUD_Test_Base, self ).__init__( methodName )
39         self.path_to_xml = path_to_xml
40         self.log = logging.getLogger( '%s CRUD_test_xml_%04d - '
41             % ( self.__class__.__name__ , ( int( self.path_to_xml[6:-4] ) ) ) )
42
43
44     def setUp( self ):
45         """
46         setting up the test
47             * load the input file to the local variable 'xml_input_stream' like a string 
48             * parse the input string as DOM Object and save it in local variable 'xml_input_DOM'
49             
50         @raise ValueError: raise ValueEror when a path is None
51         """
52         if ( self.path_to_xml is not None ) :
53             self.xml_input_stream = FileLoaderTools.load_file_to_string( self.path_to_xml )
54             self.xml_input_DOM = md.parseString( self.xml_input_stream )
55         else :
56             raise ValueError( "Path to XML is None" )
57
58
59     def tearDown( self ):
60         self.log.info( "\n ------- test has ENDED -------- \n\n\n" )
61
62
63     def test_conf_PUT( self ):
64         """
65         test_conf_PUT - conf. PUT CRUD test here is only a mock implementation 
66                    which has to be overwritten by the subclasses 
67                    for a Flow, Meter, Group test suite
68         
69         @raise NotImplementedError: always raise NotImplementedError
70         """
71         raise NotImplementedError( "Please Implement this method" )
72
73
74     def test_conf_POST( self ):
75         """
76         test_conf_POST - conf. POST create test here is only a mock implementation 
77                    which has to be overwritten by the subclasses 
78                    for a Flow, Meter, Group test suite
79         
80         @raise NotImplementedError: always raise NotImplementedError
81         """
82         raise NotImplementedError( "Please Implement this method" )
83
84
85     def test_operations_POST( self ):
86         """
87         test_operations_POST - sal service CRUD test here is only a mock implementation
88                    which has to be overwritten by the subclasses
89                    for a Flow, Meter, Group test suite
90
91         @raise NotImplementedError: always raise NotImplementedError
92         """
93         raise NotImplementedError( "Please Implement this method" )
94
95
96     # --------- Response Helper  --------------
97
98
99     def returnReverseInputTest( self, text ):
100         return ''.join( [text[len( text ) - count] for count in xrange( 1, len( text ) + 1 )] )
101
102
103     def assertDataDOM( self, orig_DOM_Doc, resp_DOM_Doc ):
104         """
105         assertDataDOM - help method for assertion the two DOM data representation
106                           e.g. the request xml and the config Data Store result
107                           has to be same always
108         @param orig_DOM_Doc: DOM Document sends to controller (e.g. from file input)
109         @param resp_DOM_Doc: DOM Document returns from response 
110         @raise AssertionError: if response has not the expected 404 code
111         """
112         origDict = XMLtoDictParserTools.parseDOM_ToDict( orig_DOM_Doc._get_documentElement() )
113         respDict = XMLtoDictParserTools.parseDOM_ToDict( resp_DOM_Doc._get_documentElement() )
114         if ( respDict != origDict ) :
115             err_msg = '\n !!! Uploaded and stored xml, are not the same\n' \
116                 ' uploaded:      %s\n stored:        %s\n differences:   %s\n' \
117                 '' % ( origDict, respDict, XMLtoDictParserTools.getDifferenceDict( origDict, respDict ) )
118             self.log.error( self._paint_msg_red( err_msg ) )
119             raise AssertionError( err_msg )
120
121
122     def _get_auth( self ):
123         return ( 'admin', 'admin' )
124
125
126     def _get_xml_result_header( self ):
127         return {'Accept': 'application/xml'}
128
129
130     def _get_xml_request_result_header( self ):
131         return {
132             'Content-Type': 'application/xml',
133             'Accept': 'application/xml',
134         }
135
136
137     def put_REST_XML_conf_request( self, put_config_url, config_data ):
138         '''
139         Method uses REST interface for PUT a request data to device from the input URL
140             * call PUT REST operation
141             * validate response status code to 204 or 200
142         @param url: URL to controller configuration DataStore for PUT method
143         @return: response from controller (expected code 204 or 200 OK)
144         @raise AssertionError: if response code is not 204 or 200 
145         '''
146         self.__log_request( put_config_url, config_data )
147         response = requests.put( put_config_url,
148                                  data = config_data,
149                                  auth = self._get_auth(),
150                                  headers = self._get_xml_request_result_header() )
151         self.__log_response( response )
152         if ( response.status_code != 204 and response.status_code != 200 ) :
153             err_msg = '\n !!! %s Status code returned %d \n' % ( sys._getframe( 1 ).f_code.co_name, response.status_code )
154             self.log.error( self._paint_msg_red( err_msg ) )
155             raise AssertionError( err_msg )
156
157         self.__time_wait_conf()
158         return response
159
160
161     def get_REST_XML_response( self, get_url ):
162         '''
163         Method uses REST interface to GET a response from the input URL
164             * call GET REST operation
165             * validate an expectation that the data is exist
166         @param get_url: URL for GETing the node data
167         @return: response from controller (expected code 200 + data in payload)
168         @raise AssertionError: if response code is not 200
169         '''
170         self.__log_request( get_url )
171         self.__time_wait_oper()
172         response = requests.get( get_url,
173                                 auth = self._get_auth(),
174                                 headers = self._get_xml_result_header() )
175         self.__log_response( response )
176         if response.status_code != 200 :
177             err_msg = '\n !!! %s Expected status code 200, but returned %d \n' % ( sys._getframe( 1 ).f_code.co_name, response.status_code )
178             self.log.error( self._paint_msg_red( err_msg ) )
179             raise AssertionError( err_msg )
180
181         self.__time_wait_conf()
182         return response
183
184
185     def get_REST_XML_deleted_response( self, get_url ):
186         '''
187         Method uses REST interface to GET the deleted data for input URL
188             * call GET REST operation
189             * validate an expectation that the data is not exist 
190         @param get_url: URL - define the deleted node
191         @return: response from controller (expect 404 Not Found - No data exists.)
192         @raise AssertionError: if response has not the expected 404 code
193         '''
194         self.__log_request( get_url )
195         response = requests.get( get_url,
196                                 auth = self._get_auth(),
197                                 headers = self._get_xml_result_header() )
198         self.__log_response( response )
199         if response.status_code != 404 :
200             err_msg = '\n !!! %s Expected status code 404, but returned %d \n' % ( sys._getframe( 1 ).f_code.co_name, response.status_code )
201             self.log.error( self._paint_msg_red( err_msg ) )
202             raise AssertionError( err_msg )
203
204         self.__time_wait_conf()
205         return response
206
207
208     def delete_REST_XML_response( self, delete_url ):
209         '''
210         Method uses REST DELETE operation for node data on input URL
211             * call DELETE REST operation
212             * validate response code 200 (deleted successful)
213         @param delete_url: URL - define the data node for delete process
214         @return: response from controller (expect 200 OK)
215         @raise AssertionError: if response has not the expected 200 code 
216         '''
217         self.__log_request( delete_url )
218         response = requests.delete( delete_url,
219                                    auth = self._get_auth(),
220                                    headers = self._get_xml_result_header() )
221         self.__log_response( response )
222         if response.status_code != 200 :
223             err_msg = '\n !!! %s Expected status code 200, but returned %d \n' % ( sys._getframe( 1 ).f_code.co_name, response.status_code )
224             self.log.error( self._paint_msg_red( err_msg ) )
225             raise AssertionError( err_msg )
226
227         self.__time_wait_conf()
228         return response
229
230
231     def post_REST_XML_request( self, post_url, post_data ):
232         '''
233         Method uses REST POST operation for node data on input URL
234             * call POST operation with input data
235             * validate response code 200 or 204 
236         @param post_url: URL - define the data node or sal-service operation
237         @return: response from controller (response code expect 200 or 204)
238         @raise AssertionError: if response has not the expected 200 or 204 code
239         '''
240         self.__log_request( post_url, post_data )
241         response = requests.post( post_url,
242                                   data = post_data,
243                                   auth = self._get_auth(),
244                                   headers = self._get_xml_request_result_header() )
245         self.__log_response( response )
246         if response.status_code != 204 and response.status_code != 200 :
247             err_msg = '\n !!! %s Status code returned %d \n' % ( sys._getframe( 1 ).f_code.co_name, response.status_code )
248             self.log.error( err_msg )
249             raise AssertionError( err_msg )
250
251         self.__time_wait_conf()
252         return response
253
254
255     def post_REST_XML_repeat_request( self, post_url, post_data ):
256         '''
257         Method uses REST POST operation for add input data on node 
258         specify by URL, but we expect to have the data, so service 
259         has to return 409 exception code
260             * call POST operation with input data
261             * validate response code 409 Conflict (data exist)
262         @param post_url: URL - define the data node
263         @return: response from controller (expected Conflict -> Data Exist)
264         @raise AssertionError: if response has not the expected 409 Conflict code  
265         '''
266         self.__log_request( post_url, post_data )
267         response = requests.post( post_url,
268                                   data = post_data,
269                                   auth = self._get_auth(),
270                                   headers = self._get_xml_request_result_header() )
271         self.__log_response( response )
272         if response.status_code != 409 :
273             err_msg = '\n !!! %s Expected status code 409, but returned %d \n' % ( sys._getframe( 1 ).f_code.co_name, response.status_code )
274             self.log.error( self._paint_msg_red( err_msg ) )
275             raise AssertionError( err_msg )
276
277         self.__time_wait_conf()
278         return response
279
280
281     # --------- LOGGING HELP METHODS --------
282     def __log_request( self, url, data = None ):
283         self.log.info( ' Running method is "%s"' % sys._getframe( 1 ).f_code.co_name )
284         self.log.info( ' REQUEST is sending to URL : {0} '.format( self._paint_msg_blue( url ) ) )
285         if data is not None :
286             self.log.debug( ' REQUEST data : \n\n%s\n' % ( self._paint_msg_green( data ) ) )
287         else :
288             self.log.debug( ' REQUEST data: %s \n' % ( self._paint_msg_green( 'None' ) ) )
289
290
291     def __log_response( self, response ):
292         self.log.info( ' Running method is "%s" ' % sys._getframe( 1 ).f_code.co_name )
293         self.log.info( ' RECEIVED status code: {0} '.format( self._paint_msg_magenta( response.status_code ) ) )
294         self.log.debug( ' RECEIVED data : \n\n%s\n' % self._paint_msg_green( response.content ) )
295
296
297     def __time_wait_oper( self ):
298         self.log.info( '......... Waiting for operational DataStore %s sec. ......... ' % self.CONTROLLER_OPERATION_DELAY )
299         time.sleep( self.CONTROLLER_OPERATION_DELAY )
300
301
302     def __time_wait_conf( self ):
303         self.log.info( '......... Waiting for controller %s sec. ......... ' % self.CONTROLLER_DELAY )
304         time.sleep( self.CONTROLLER_DELAY )
305
306
307     def _paint_msg_green( self, msg ):
308         return self.__paint_msg( msg, 2 )
309
310
311     def _paint_msg_blue ( self, msg ):
312         return self.__paint_msg( msg, ColorEnum.BLUE )
313
314
315     def _paint_msg_magenta ( self, msg ):
316         return self.__paint_msg( msg, ColorEnum.MAGENTA )
317
318
319     def _paint_msg_cyan ( self, msg ):
320         return self.__paint_msg( msg, ColorEnum.CYAN )
321
322
323     def _paint_msg_red ( self, msg ):
324         return self.__paint_msg( msg, ColorEnum.RED )
325
326
327     def _paint_msg_yellow( self, msg ):
328         return self.__paint_msg( msg, ColorEnum.YELLOW )
329
330
331     def __paint_msg ( self, msg, colorNr ):
332         if ( self.COLORING == 1 ) :
333             color = '\x1b[3%dm' % colorNr
334             return '%s %s %s' % ( color, msg, '\x1b[0m' )
335         else :
336             return msg