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