Merge "Update Functest documentation for Euphrates"
[functest.git] / functest / opnfv_tests / vnf / ims / ixia / utils / IxLoadUtils.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2017 IXIA and others.
4 #
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9
10 import requests
11 import sys
12 import time
13 import logging
14 from IxRestUtils import formatDictToJSONPayload
15
16 kActionStateFinished = 'finished'
17 kActionStatusSuccessful = 'Successful'
18 kActionStatusError = 'Error'
19 kTestStateUnconfigured = 'Unconfigured'
20
21 logger = logging.getLogger(__name__)
22
23
24 def stripApiAndVersionFromURL(url):
25
26     # remove the slash (if any) at the beginning of the url
27
28     if url[0] == '/':
29         url = url[1:]
30
31     urlElements = url.split('/')
32     if 'api' in url:
33
34         # strip the api/v0 part of the url
35
36         urlElements = urlElements[2:]
37
38     return '/'.join(urlElements)
39
40
41 def waitForActionToFinish(connection, replyObj, actionUrl):
42     """
43     This method waits for an action to finish executing. after a POST request
44     is sent in order to start an action, The HTTP reply will contain,
45     in the header, a 'location' field, that contains an URL.
46     The action URL contains the status of the action. we perform a GET on that
47     URL every 0.5 seconds until the action finishes with a success.
48     If the action fails, we will throw an error and
49     print the action's error message.
50     """
51
52     actionResultURL = replyObj.headers.get('location')
53     if actionResultURL:
54         actionResultURL = stripApiAndVersionFromURL(actionResultURL)
55         actionFinished = False
56
57         while not actionFinished:
58             actionStatusObj = connection.httpGet(actionResultURL)
59
60             if actionStatusObj.state == kActionStateFinished:
61                 if actionStatusObj.status == kActionStatusSuccessful:
62                     actionFinished = True
63                 else:
64                     errorMsg = "Error while executing action '%s'." \
65                         % actionUrl
66
67                     if actionStatusObj.status == kActionStatusError:
68                         errorMsg += actionStatusObj.error
69
70                     print errorMsg
71
72                     sys.exit(1)
73             else:
74                 time.sleep(0.1)
75
76
77 def performGenericOperation(connection, url, payloadDict):
78     """
79     This will perform a generic operation on the given url,
80     it will wait for it to finish.
81     """
82
83     data = formatDictToJSONPayload(payloadDict)
84     reply = connection.httpPost(url=url, data=data)
85
86     waitForActionToFinish(connection, reply, url)
87
88     return reply
89
90
91 def performGenericPost(connection, listUrl, payloadDict):
92     """
93     This will perform a generic POST method on a given url
94     """
95
96     data = formatDictToJSONPayload(payloadDict)
97
98     reply = connection.httpPost(url=listUrl, data=data)
99     try:
100         newObjPath = reply.headers['location']
101     except:
102         raise Exception('Location header is not present. \
103                         Please check if the action was created successfully.')
104
105     newObjID = newObjPath.split('/')[-1]
106     return newObjID
107
108
109 def performGenericDelete(connection, listUrl, payloadDict):
110     """
111     This will perform a generic DELETE method on a given url
112     """
113
114     data = formatDictToJSONPayload(payloadDict)
115
116     reply = connection.httpDelete(url=listUrl, data=data)
117     return reply
118
119
120 def performGenericPatch(connection, url, payloadDict):
121     """
122     This will perform a generic PATCH method on a given url
123     """
124
125     data = formatDictToJSONPayload(payloadDict)
126
127     reply = connection.httpPatch(url=url, data=data)
128     return reply
129
130
131 def createSession(connection, ixLoadVersion):
132     """
133     This method is used to create a new session.
134     It will return the url of the newly created session
135     """
136
137     sessionsUrl = 'sessions'
138     data = {'ixLoadVersion': ixLoadVersion}
139
140     sessionId = performGenericPost(connection, sessionsUrl, data)
141
142     newSessionUrl = '%s/%s' % (sessionsUrl, sessionId)
143     startSessionUrl = '%s/operations/start' % newSessionUrl
144
145     # start the session
146
147     performGenericOperation(connection, startSessionUrl, {})
148
149     logger.debug('Created session no %s' % sessionId)
150
151     return newSessionUrl
152
153
154 def deleteSession(connection, sessionUrl):
155     """
156     This method is used to delete an existing session.
157     """
158
159     deleteParams = {}
160     performGenericDelete(connection, sessionUrl, deleteParams)
161
162
163 def uploadFile(connection, url, fileName, uploadPath, overwrite=True):
164     headers = {'Content-Type': 'multipart/form-data'}
165     params = {'overwrite': overwrite, 'uploadPath': uploadPath}
166
167     logger.debug('Uploading...')
168     try:
169         with open(fileName, 'rb') as f:
170             resp = requests.post(url, data=f, params=params,
171                                  headers=headers)
172     except requests.exceptions.ConnectionError, e:
173         raise Exception('Upload file failed. Received connection error. \
174                         One common cause for this error is the size of the \
175                         file to be uploaded.The web server sets a limit of 1GB\
176                         for the uploaded file size. \
177                         Received the following error: %s' % str(e))
178     except IOError, e:
179         raise Exception('Upload file failed. Received IO error: %s'
180                         % str(e))
181     except Exception:
182         raise Exception('Upload file failed. Received the following error: %s'
183                         % str(e))
184     else:
185         logger.debug('Upload file finished.')
186         logger.debug('Response status code %s' % resp.status_code)
187         logger.debug('Response text %s' % resp.text)
188
189
190 def loadRepository(connection, sessionUrl, rxfFilePath):
191     """
192     This method will perform a POST request to load a repository.
193     """
194
195     loadTestUrl = '%s/ixload/test/operations/loadTest' % sessionUrl
196     data = {'fullPath': rxfFilePath}
197
198     performGenericOperation(connection, loadTestUrl, data)
199
200
201 def saveRxf(connection, sessionUrl, rxfFilePath):
202     """
203     This method saves the current rxf to the disk of the machine on
204     which the IxLoad instance is running.
205     """
206
207     saveRxfUrl = '%s/ixload/test/operations/saveAs' % sessionUrl
208     rxfFilePath = rxfFilePath.replace('\\', '\\\\')
209     data = {'fullPath': rxfFilePath, 'overWrite': 1}
210
211     performGenericOperation(connection, saveRxfUrl, data)
212
213
214 def runTest(connection, sessionUrl):
215     """
216     This method is used to start the currently loaded test.
217     After starting the 'Start Test' action, wait for the action to complete.
218     """
219
220     startRunUrl = '%s/ixload/test/operations/runTest' % sessionUrl
221     data = {}
222
223     performGenericOperation(connection, startRunUrl, data)
224
225
226 def getTestCurrentState(connection, sessionUrl):
227     """
228     This method gets the test current state.
229     (for example - running, unconfigured, ..)
230     """
231
232     activeTestUrl = '%s/ixload/test/activeTest' % sessionUrl
233     testObj = connection.httpGet(activeTestUrl)
234
235     return testObj.currentState
236
237
238 def getTestRunError(connection, sessionUrl):
239     """
240     This method gets the error that appeared during the last test run.
241     If no error appeared (the test ran successfully),
242     the return value will be 'None'.
243     """
244
245     activeTestUrl = '%s/ixload/test/activeTest' % sessionUrl
246     testObj = connection.httpGet(activeTestUrl)
247
248     return testObj.testRunError
249
250
251 def waitForTestToReachUnconfiguredState(connection, sessionUrl):
252     """
253     This method waits for the current test to reach the 'Unconfigured' state.
254     """
255
256     while getTestCurrentState(connection, sessionUrl) \
257             != kTestStateUnconfigured:
258         time.sleep(0.1)
259
260
261 def pollStats(connection, sessionUrl, watchedStatsDict, pollingInterval=4):
262     """
263     This method is used to poll the stats.
264     Polling stats is per request but this method does a continuous poll.
265     """
266
267     statSourceList = watchedStatsDict.keys()
268     statsDict = {}
269
270     collectedTimestamps = {}
271     testIsRunning = True
272
273     # check stat sources
274
275     for statSource in statSourceList[:]:
276         statSourceUrl = '%s/ixload/stats/%s/values' % (sessionUrl, statSource)
277         statSourceReply = connection.httpRequest('GET', statSourceUrl)
278         if statSourceReply.status_code != 200:
279             logger.debug("Warning - Stat source '%s' does not exist. \
280                          Will ignore it." % statSource)
281             statSourceList.remove(statSource)
282
283     # check the test state, and poll stats while the test is still running
284
285     while testIsRunning:
286
287         # the polling interval is configurable.
288         # by default, it's set to 4 seconds
289
290         time.sleep(pollingInterval)
291
292         for statSource in statSourceList:
293             valuesUrl = '%s/ixload/stats/%s/values' % (sessionUrl, statSource)
294
295             valuesObj = connection.httpGet(valuesUrl)
296             valuesDict = valuesObj.getOptions()
297
298             # get just the new timestamps - that were not previously
299             # retrieved in another stats polling iteration
300
301             newTimestamps = [int(timestamp) for timestamp in
302                              valuesDict.keys() if timestamp
303                              not in collectedTimestamps.get(statSource,
304                              [])]
305             newTimestamps.sort()
306
307             for timestamp in newTimestamps:
308                 timeStampStr = str(timestamp)
309
310                 collectedTimestamps.setdefault(statSource, []).append(
311                                                                   timeStampStr)
312
313                 timestampDict = statsDict.setdefault(statSource,
314                                                      {}).setdefault(
315                                                                 timestamp, {})
316
317                 # save the values for the current timestamp,
318                 # and later print them
319
320                 logger.info(' -- ')
321                 for (caption, value) in \
322                         valuesDict[timeStampStr].getOptions().items():
323                     if caption in watchedStatsDict[statSource]:
324                         logger.info(' %s -> %s' % (caption, value))
325                         timestampDict[caption] = value
326
327         testIsRunning = getTestCurrentState(connection, sessionUrl) \
328             == 'Running'
329
330     logger.debug('Stopped receiving stats.')
331     return timestampDict
332
333
334 def clearChassisList(connection, sessionUrl):
335     """
336     This method is used to clear the chassis list.
337     After execution no chassis should be available in the chassisListself.
338     """
339
340     chassisListUrl = '%s/ixload/chassischain/chassisList' % sessionUrl
341     deleteParams = {}
342     performGenericDelete(connection, chassisListUrl, deleteParams)
343
344
345 def configureLicenseServer(connection, sessionUrl, licenseServerIp):
346     """
347     This method is used to clear the chassis list.
348     After execution no chassis should be available in the chassisList.
349     """
350
351     chassisListUrl = '%s/ixload/preferences' % sessionUrl
352     patchParams = {'licenseServer': licenseServerIp}
353     performGenericPatch(connection, chassisListUrl, patchParams)
354
355
356 def addChassisList(connection, sessionUrl, chassisList):
357     """
358     This method is used to add one or more chassis to the chassis list.
359     """
360
361     chassisListUrl = '%s/ixload/chassisChain/chassisList' % sessionUrl
362
363     for chassisName in chassisList:
364         data = {'name': chassisName}
365         chassisId = performGenericPost(connection, chassisListUrl, data)
366
367         # refresh the chassis
368
369         refreshConnectionUrl = '%s/%s/operations/refreshConnection' \
370             % (chassisListUrl, chassisId)
371         performGenericOperation(connection, refreshConnectionUrl, {})
372
373
374 def assignPorts(connection, sessionUrl, portListPerCommunity):
375     """
376     This method is used to assign ports from a connected chassis
377     to the required NetTraffics.
378     """
379
380     communtiyListUrl = '%s/ixload/test/activeTest/communityList' \
381         % sessionUrl
382
383     communityList = connection.httpGet(url=communtiyListUrl)
384
385     for community in communityList:
386         portListForCommunity = portListPerCommunity.get(community.name)
387
388         portListUrl = '%s/%s/network/portList' % (communtiyListUrl,
389                                                   community.objectID)
390
391         if portListForCommunity:
392             for portTuple in portListForCommunity:
393                 (chassisId, cardId, portId) = portTuple
394                 paramDict = {'chassisId': chassisId, 'cardId': cardId,
395                              'portId': portId}
396
397                 performGenericPost(connection, portListUrl, paramDict)