In this section of the lab, you will learn about CXTM's API and how to create Python code to interact with the API. The first thing you need to do is generate your own API token. That token will then be used as a secret environment variable in GitLab that you will access in the pipeline. This is a best practice as to not expose your API token.
CXTM's API is driven based on identifiers for objects in the API. There will be multiple code functions for you to develop in order to query for your specific project ID and associated batches that you created earlier in the lab. All of this will be done dynamically based on what you pass into the code. Lastly, you will write the fucntion to drive the desired batch and a very simple backoff and retry function to control if the pipeline should pass or fail in coorelation with your test cases.
In preparation to store your CXTM API token as an environment variable in GitLab, navigate to CICD settings in your GitLab project:
In the center of your screen:
Launch the Add Variable pop-up in preparation to add your CXTM API token as a variable:
In the Add Variable pop-up, fillout the fields as described below:
In your browser, return to CXTM:
On the right-side of your screen,
After generating your CXTM API token:
Return to GitLab:
Placeholder
Return to your VSCode Terminal and use the code
keyword to open a YAML file called cxtm-pipeline-script.py
.
code -r /home/cisco/CiscoLive/LTROPS-2711/tests/cxtm-pipeline-script.py
The script below defines one such method of driving CXTM's REST AP, specifcally projects and batch API endpoints, using the Python requests library. Additionally, there a few other Python packages leveraged along with requests:
import argparse
import os
import sys
import requests
import json
import time
import random
from datetime import datetime
requests.packages.urllib3.disable_warnings()
CXTM_API_KEY = os.environ.get('CXTM_API_KEY')
HEADERS = {
'X-TM2-API-KEY': CXTM_API_KEY,
'Content-Type': 'application-json',
'Accept': '*/*'
}
FORM_HEADERS = {
'X-TM2-API-KEY': CXTM_API_KEY,
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': '*/*'
}
def get_project_id(project_name):
try:
url = f"{CXTM_BASE_URL}/projects/writable"
response = requests.request("GET", url, headers=HEADERS, verify=False)
response_dict = json.loads(response.text)
projects = response_dict['data']
for project in projects:
if project['name'] == project_name:
project_id = project['id']
return project_id
except Exception:
print(f"Project {project_name} is invalid!")
sys.exit(1)
def get_batch_id(project_id, batch_name):
try:
url = f"{CXTM_BASE_URL}/projects/{project_id}/batches"
response = requests.request("GET", url, headers=HEADERS, verify=False)
response_dict = json.loads(response.text)
batches = response_dict['data']
for batch in batches:
if batch['title'] in batch_name:
batch_id = batch['id']
return batch_id
except Exception:
print(f"Batch {batch_name} is invalid!")
sys.exit(1)
def run_batch(project_id, batch_name, batch_id):
tprint(f"Starting {batch_name} test cases...")
print()
url = f"{CXTM_BASE_URL}/batches/{batch_id}/run"
raw_data = 'is_default_topology=true'
response = requests.request("POST", url, headers=FORM_HEADERS, data=raw_data, verify=False)
response_dict = json.loads(response.text)
batch_run_id = response_dict[0]['batchrun_id']
retry_with_backoff(project_id, batch_id, batch_run_id)
print()
tprint("Testing ended...")
def tprint(msg):
timestamp = datetime.now().strftime('%M:%S.%f')[:-3]
print(timestamp, msg)
def retry_with_backoff(project_id, batch_id, batch_run_id, backoff_in_seconds=30):
url = f"{CXTM_BASE_URL}/projects/{project_id}/batches/{batch_id}/runs/{batch_run_id}"
x = 0
testcase_status_list = [None]
while None in testcase_status_list:
testcase_status_list = []
response = requests.request("GET", url, headers=HEADERS, verify=False)
response_dict = json.loads(response.text)
batch_testcases = response_dict['data']
for batch_testcase in batch_testcases:
testcase_status_list.append(batch_testcase['result_rollup'])
if batch_testcase['result_rollup'] is None:
tprint(f"Test case {batch_testcase['name']} is QUEUED")
elif batch_testcase['result_rollup'] in ('passed', 'aborted'):
tprint(f"Test case {batch_testcase['name']} {batch_testcase['result_rollup']} execution")
elif batch_testcase['result_rollup'] in ('failed', 'errored'):
raise Exception(f"Test case {batch_testcase['name']} {batch_testcase['result_rollup']} execution\n")
if None in testcase_status_list:
sleep = (backoff_in_seconds * 2**x + random.uniform(0, 1))
tprint(f"Backoff: {sleep}")
time.sleep(sleep)
def main():
global CXTM_BASE_URL
parser = argparse.ArgumentParser(
description='CXTM Batch Pipeline Script',
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument('-c', '--cxtm', action='store', help='CXTM IP Address')
parser.add_argument('-p', '--project', action='store', help='CXTM Project Name')
parser.add_argument('-b', '--batch', action='store', help='CXTM Batch Name')
args = parser.parse_args()
CXTM_BASE_URL = f"https://{args.cxtm}/api/v1"
project_id = get_project_id(args.project)
batch_id = get_batch_id(project_id, args.batch)
run_batch(project_id, args.batch, batch_id)
if __name__ == '__main__':
sys.exit(main())
Be sure to save your file! Not saving will result in your code not executing.
Continue to the next section to create your GitLab CI pipeline fill which will specify what your pipeline does.