3 # Copyright (c) 2017 IXIA and others.
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
14 from IxRestUtils import formatDictToJSONPayload
16 kActionStateFinished = 'finished'
17 kActionStatusSuccessful = 'Successful'
18 kActionStatusError = 'Error'
19 kTestStateUnconfigured = 'Unconfigured'
21 logger = logging.getLogger(__name__)
24 def stripApiAndVersionFromURL(url):
26 # remove the slash (if any) at the beginning of the url
31 urlElements = url.split('/')
34 # strip the api/v0 part of the url
36 urlElements = urlElements[2:]
38 return '/'.join(urlElements)
41 def waitForActionToFinish(connection, replyObj, actionUrl):
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.
52 actionResultURL = replyObj.headers.get('location')
54 actionResultURL = stripApiAndVersionFromURL(actionResultURL)
55 actionFinished = False
57 while not actionFinished:
58 actionStatusObj = connection.httpGet(actionResultURL)
60 if actionStatusObj.state == kActionStateFinished:
61 if actionStatusObj.status == kActionStatusSuccessful:
64 errorMsg = "Error while executing action '%s'." \
67 if actionStatusObj.status == kActionStatusError:
68 errorMsg += actionStatusObj.error
77 def performGenericOperation(connection, url, payloadDict):
79 This will perform a generic operation on the given url,
80 it will wait for it to finish.
83 data = formatDictToJSONPayload(payloadDict)
84 reply = connection.httpPost(url=url, data=data)
86 waitForActionToFinish(connection, reply, url)
91 def performGenericPost(connection, listUrl, payloadDict):
93 This will perform a generic POST method on a given url
96 data = formatDictToJSONPayload(payloadDict)
98 reply = connection.httpPost(url=listUrl, data=data)
100 newObjPath = reply.headers['location']
102 raise Exception('Location header is not present. \
103 Please check if the action was created successfully.')
105 newObjID = newObjPath.split('/')[-1]
109 def performGenericDelete(connection, listUrl, payloadDict):
111 This will perform a generic DELETE method on a given url
114 data = formatDictToJSONPayload(payloadDict)
116 reply = connection.httpDelete(url=listUrl, data=data)
120 def performGenericPatch(connection, url, payloadDict):
122 This will perform a generic PATCH method on a given url
125 data = formatDictToJSONPayload(payloadDict)
127 reply = connection.httpPatch(url=url, data=data)
131 def createSession(connection, ixLoadVersion):
133 This method is used to create a new session.
134 It will return the url of the newly created session
137 sessionsUrl = 'sessions'
138 data = {'ixLoadVersion': ixLoadVersion}
140 sessionId = performGenericPost(connection, sessionsUrl, data)
142 newSessionUrl = '%s/%s' % (sessionsUrl, sessionId)
143 startSessionUrl = '%s/operations/start' % newSessionUrl
147 performGenericOperation(connection, startSessionUrl, {})
149 logger.debug('Created session no %s' % sessionId)
154 def deleteSession(connection, sessionUrl):
156 This method is used to delete an existing session.
160 performGenericDelete(connection, sessionUrl, deleteParams)
163 def uploadFile(connection, url, fileName, uploadPath, overwrite=True):
164 headers = {'Content-Type': 'multipart/form-data'}
165 params = {'overwrite': overwrite, 'uploadPath': uploadPath}
167 logger.debug('Uploading...')
169 with open(fileName, 'rb') as f:
170 resp = requests.post(url, data=f, params=params,
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))
179 raise Exception('Upload file failed. Received IO error: %s'
182 raise Exception('Upload file failed. Received the following error: %s'
185 logger.debug('Upload file finished.')
186 logger.debug('Response status code %s' % resp.status_code)
187 logger.debug('Response text %s' % resp.text)
190 def loadRepository(connection, sessionUrl, rxfFilePath):
192 This method will perform a POST request to load a repository.
195 loadTestUrl = '%s/ixload/test/operations/loadTest' % sessionUrl
196 data = {'fullPath': rxfFilePath}
198 performGenericOperation(connection, loadTestUrl, data)
201 def saveRxf(connection, sessionUrl, rxfFilePath):
203 This method saves the current rxf to the disk of the machine on
204 which the IxLoad instance is running.
207 saveRxfUrl = '%s/ixload/test/operations/saveAs' % sessionUrl
208 rxfFilePath = rxfFilePath.replace('\\', '\\\\')
209 data = {'fullPath': rxfFilePath, 'overWrite': 1}
211 performGenericOperation(connection, saveRxfUrl, data)
214 def runTest(connection, sessionUrl):
216 This method is used to start the currently loaded test.
217 After starting the 'Start Test' action, wait for the action to complete.
220 startRunUrl = '%s/ixload/test/operations/runTest' % sessionUrl
223 performGenericOperation(connection, startRunUrl, data)
226 def getTestCurrentState(connection, sessionUrl):
228 This method gets the test current state.
229 (for example - running, unconfigured, ..)
232 activeTestUrl = '%s/ixload/test/activeTest' % sessionUrl
233 testObj = connection.httpGet(activeTestUrl)
235 return testObj.currentState
238 def getTestRunError(connection, sessionUrl):
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'.
245 activeTestUrl = '%s/ixload/test/activeTest' % sessionUrl
246 testObj = connection.httpGet(activeTestUrl)
248 return testObj.testRunError
251 def waitForTestToReachUnconfiguredState(connection, sessionUrl):
253 This method waits for the current test to reach the 'Unconfigured' state.
256 while getTestCurrentState(connection, sessionUrl) \
257 != kTestStateUnconfigured:
261 def pollStats(connection, sessionUrl, watchedStatsDict, pollingInterval=4):
263 This method is used to poll the stats.
264 Polling stats is per request but this method does a continuous poll.
267 statSourceList = watchedStatsDict.keys()
270 collectedTimestamps = {}
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)
283 # check the test state, and poll stats while the test is still running
287 # the polling interval is configurable.
288 # by default, it's set to 4 seconds
290 time.sleep(pollingInterval)
292 for statSource in statSourceList:
293 valuesUrl = '%s/ixload/stats/%s/values' % (sessionUrl, statSource)
295 valuesObj = connection.httpGet(valuesUrl)
296 valuesDict = valuesObj.getOptions()
298 # get just the new timestamps - that were not previously
299 # retrieved in another stats polling iteration
301 newTimestamps = [int(timestamp) for timestamp in
302 valuesDict.keys() if timestamp
303 not in collectedTimestamps.get(statSource,
307 for timestamp in newTimestamps:
308 timeStampStr = str(timestamp)
310 collectedTimestamps.setdefault(statSource, []).append(
313 timestampDict = statsDict.setdefault(statSource,
317 # save the values for the current timestamp,
318 # and later print them
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
327 testIsRunning = getTestCurrentState(connection, sessionUrl) \
330 logger.debug('Stopped receiving stats.')
334 def clearChassisList(connection, sessionUrl):
336 This method is used to clear the chassis list.
337 After execution no chassis should be available in the chassisListself.
340 chassisListUrl = '%s/ixload/chassischain/chassisList' % sessionUrl
342 performGenericDelete(connection, chassisListUrl, deleteParams)
345 def configureLicenseServer(connection, sessionUrl, licenseServerIp):
347 This method is used to clear the chassis list.
348 After execution no chassis should be available in the chassisList.
351 chassisListUrl = '%s/ixload/preferences' % sessionUrl
352 patchParams = {'licenseServer': licenseServerIp}
353 performGenericPatch(connection, chassisListUrl, patchParams)
356 def addChassisList(connection, sessionUrl, chassisList):
358 This method is used to add one or more chassis to the chassis list.
361 chassisListUrl = '%s/ixload/chassisChain/chassisList' % sessionUrl
363 for chassisName in chassisList:
364 data = {'name': chassisName}
365 chassisId = performGenericPost(connection, chassisListUrl, data)
367 # refresh the chassis
369 refreshConnectionUrl = '%s/%s/operations/refreshConnection' \
370 % (chassisListUrl, chassisId)
371 performGenericOperation(connection, refreshConnectionUrl, {})
374 def assignPorts(connection, sessionUrl, portListPerCommunity):
376 This method is used to assign ports from a connected chassis
377 to the required NetTraffics.
380 communtiyListUrl = '%s/ixload/test/activeTest/communityList' \
383 communityList = connection.httpGet(url=communtiyListUrl)
385 for community in communityList:
386 portListForCommunity = portListPerCommunity.get(community.name)
388 portListUrl = '%s/%s/network/portList' % (communtiyListUrl,
391 if portListForCommunity:
392 for portTuple in portListForCommunity:
393 (chassisId, cardId, portId) = portTuple
394 paramDict = {'chassisId': chassisId, 'cardId': cardId,
397 performGenericPost(connection, portListUrl, paramDict)