From 650b2dc6eb510b28943873a4dec2cc022e168d8b Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 18 Dec 2024 14:22:38 +0100 Subject: [PATCH] A script to run CI tests locally. Added ./run_locally.sh script. The script takes one argument being the testcase that one wants to run. The testcase is run locally. This means that some testcases that utilize hardware resources will not run. However most if not all rfsimulator testcases should run as in CI. To this end, the CI python scripting framework was updated, adding --local flag which changes the script behavior as follows: - overrides and XML elements to 'localhost' so all commands are executed locally - Avoid running image pull, image cleanup and workspace creation steps of the scripts: user is responsible for that --- ci-scripts/args_parse.py | 6 +++- ci-scripts/cls_cmd.py | 2 ++ ci-scripts/helpreadme.py | 2 ++ ci-scripts/main.py | 60 ++++++++++++++++++++++++++------------- ci-scripts/run_locally.sh | 35 +++++++++++++++++++++++ 5 files changed, 84 insertions(+), 21 deletions(-) create mode 100755 ci-scripts/run_locally.sh diff --git a/ci-scripts/args_parse.py b/ci-scripts/args_parse.py index c1a13eb9bf..cbf4159da2 100644 --- a/ci-scripts/args_parse.py +++ b/ci-scripts/args_parse.py @@ -47,6 +47,7 @@ def ArgsParse(argvs,CiTestObj,RAN,HTML,EPC,CONTAINERS,HELP,SCA,PHYSIM,CLUSTER): py_param_file_present = False py_params={} + force_local = False while len(argvs) > 1: myArgv = argvs.pop(1) # 0th is this file's name @@ -54,6 +55,9 @@ def ArgsParse(argvs,CiTestObj,RAN,HTML,EPC,CONTAINERS,HELP,SCA,PHYSIM,CLUSTER): if re.match('^\-\-help$', myArgv, re.IGNORECASE): HELP.GenericHelp(CONST.Version) sys.exit(0) + if re.match('^\-\-local$', myArgv, re.IGNORECASE): + force_local = True + #--apply= as parameters file, to replace inline parameters elif re.match('^\-\-Apply=(.+)$', myArgv, re.IGNORECASE): @@ -271,4 +275,4 @@ def ArgsParse(argvs,CiTestObj,RAN,HTML,EPC,CONTAINERS,HELP,SCA,PHYSIM,CLUSTER): HELP.GenericHelp(CONST.Version) sys.exit('Invalid Parameter: ' + myArgv) - return py_param_file_present, py_params, mode + return py_param_file_present, py_params, mode, force_local diff --git a/ci-scripts/cls_cmd.py b/ci-scripts/cls_cmd.py index 8784b70831..b338a6938f 100644 --- a/ci-scripts/cls_cmd.py +++ b/ci-scripts/cls_cmd.py @@ -217,6 +217,8 @@ class RemoteCmd(Cmd): return client def _lookup_ssh_config(hostname): + if is_local(hostname): + raise ValueError("Using localhost as SSH target is not allowed: use LocalCmd instead.") ssh_config = paramiko.SSHConfig() user_config_file = os.path.expanduser("~/.ssh/config") if os.path.exists(user_config_file): diff --git a/ci-scripts/helpreadme.py b/ci-scripts/helpreadme.py index 9ecba905ac..e1640fc899 100644 --- a/ci-scripts/helpreadme.py +++ b/ci-scripts/helpreadme.py @@ -43,6 +43,8 @@ def GenericHelp(vers): print(' InitiateHtml, FinalizeHtml') print(' TerminateeNB, TerminateHSS, TerminateMME, TerminateSPGW') print(' LogCollectBuild, LogCollecteNB, LogCollectHSS, LogCollectMME, LogCollectSPGW, LogCollectPing, LogCollectIperf') + print(' --local Force local execution: rewrites the test xml script before running to always execute on localhost. Assumes') + print(' images are available locally, will not remove any images and will run inside the current repo directory') def GitSrvHelp(repository,branch,commit,mergeallow,targetbranch): print(' --ranRepository=[OAI RAN Repository URL] -- ' + repository) diff --git a/ci-scripts/main.py b/ci-scripts/main.py index bba0f3f57f..7a625eaa7d 100644 --- a/ci-scripts/main.py +++ b/ci-scripts/main.py @@ -212,13 +212,17 @@ def ExecuteActionWithParam(action): elif action == 'Initialize_UE' or action == 'Attach_UE' or action == 'Detach_UE' or action == 'Terminate_UE' or action == 'CheckStatusUE' or action == 'DataEnable_UE' or action == 'DataDisable_UE': CiTestObj.ue_ids = test.findtext('id').split(' ') - if test.findtext('nodes'): - CiTestObj.nodes = test.findtext('nodes').split(' ') - if len(CiTestObj.ue_ids) != len(CiTestObj.nodes): - logging.error('Number of Nodes are not equal to the total number of UEs') - sys.exit("Mismatch in number of Nodes and UIs") + if force_local: + # Change all execution targets to localhost + CiTestObj.nodes = ['localhost'] * len(CiTestObj.ue_ids) else: - CiTestObj.nodes = [None] * len(CiTestObj.ue_ids) + if test.findtext('nodes'): + CiTestObj.nodes = test.findtext('nodes').split(' ') + if len(CiTestObj.ue_ids) != len(CiTestObj.nodes): + logging.error('Number of Nodes are not equal to the total number of UEs') + sys.exit("Mismatch in number of Nodes and UIs") + else: + CiTestObj.nodes = [None] * len(CiTestObj.ue_ids) if action == 'Initialize_UE': success = CiTestObj.InitializeUE(HTML) elif action == 'Attach_UE': @@ -238,13 +242,17 @@ def ExecuteActionWithParam(action): CiTestObj.ping_args = test.findtext('ping_args') CiTestObj.ping_packetloss_threshold = test.findtext('ping_packetloss_threshold') CiTestObj.ue_ids = test.findtext('id').split(' ') - if test.findtext('nodes'): - CiTestObj.nodes = test.findtext('nodes').split(' ') - if len(CiTestObj.ue_ids) != len(CiTestObj.nodes): - logging.error('Number of Nodes are not equal to the total number of UEs') - sys.exit("Mismatch in number of Nodes and UIs") + if force_local: + # Change all execution targets to localhost + CiTestObj.nodes = ['localhost'] * len(CiTestObj.ue_ids) else: - CiTestObj.nodes = [None] * len(CiTestObj.ue_ids) + if test.findtext('nodes'): + CiTestObj.nodes = test.findtext('nodes').split(' ') + if len(CiTestObj.ue_ids) != len(CiTestObj.nodes): + logging.error('Number of Nodes are not equal to the total number of UEs') + sys.exit("Mismatch in number of Nodes and UIs") + else: + CiTestObj.nodes = [None] * len(CiTestObj.ue_ids) ping_rttavg_threshold = test.findtext('ping_rttavg_threshold') or '' success = CiTestObj.Ping(HTML,EPC,CONTAINERS) @@ -252,15 +260,19 @@ def ExecuteActionWithParam(action): CiTestObj.iperf_args = test.findtext('iperf_args') CiTestObj.ue_ids = test.findtext('id').split(' ') CiTestObj.svr_id = test.findtext('svr_id') or None - if test.findtext('nodes'): - CiTestObj.nodes = test.findtext('nodes').split(' ') - if len(CiTestObj.ue_ids) != len(CiTestObj.nodes): - logging.error('Number of Nodes are not equal to the total number of UEs') - sys.exit("Mismatch in number of Nodes and UIs") + if force_local: + # Change all execution targets to localhost + CiTestObj.nodes = ['localhost'] * len(CiTestObj.ue_ids) else: - CiTestObj.nodes = [None] * len(CiTestObj.ue_ids) + if test.findtext('nodes'): + CiTestObj.nodes = test.findtext('nodes').split(' ') + if len(CiTestObj.ue_ids) != len(CiTestObj.nodes): + logging.error('Number of Nodes are not equal to the total number of UEs') + sys.exit("Mismatch in number of Nodes and UIs") + else: + CiTestObj.nodes = [None] * len(CiTestObj.ue_ids) if test.findtext('svr_node'): - CiTestObj.svr_node = test.findtext('svr_node') + CiTestObj.svr_node = test.findtext('svr_node') if not force_local else 'localhost' CiTestObj.iperf_packetloss_threshold = test.findtext('iperf_packetloss_threshold') CiTestObj.iperf_bitrate_threshold = test.findtext('iperf_bitrate_threshold') or '90' CiTestObj.iperf_profile = test.findtext('iperf_profile') or 'balanced' @@ -356,6 +368,9 @@ def ExecuteActionWithParam(action): elif action == 'Undeploy_Object': success = CONTAINERS.UndeployObject(HTML, RAN) elif action == 'Create_Workspace': + if force_local: + # Do not create a working directory when running locally. Current repo directory will be used + return True success = CONTAINERS.Create_Workspace(HTML) elif action == 'Run_Physim': @@ -375,6 +390,9 @@ def ExecuteActionWithParam(action): success = CONTAINERS.Push_Image_to_Local_Registry(HTML, svr_id) elif action == 'Pull_Local_Registry' or action == 'Clean_Test_Server_Images': + if force_local: + # Do not pull or remove images when running locally. User is supposed to handle image creation & cleanup + return True svr_id = test.findtext('svr_id') images = test.findtext('images').split() # hack: for FlexRIC, we need to overwrite the tag to use @@ -463,7 +481,9 @@ CLUSTER = cls_cluster.Cluster() #----------------------------------------------------------- import args_parse -py_param_file_present, py_params, mode = args_parse.ArgsParse(sys.argv,CiTestObj,RAN,HTML,EPC,CONTAINERS,HELP,SCA,PHYSIM,CLUSTER) +# Force local execution, move all execution targets to localhost +force_local = False +py_param_file_present, py_params, mode, force_local = args_parse.ArgsParse(sys.argv,CiTestObj,RAN,HTML,EPC,CONTAINERS,HELP,SCA,PHYSIM,CLUSTER) diff --git a/ci-scripts/run_locally.sh b/ci-scripts/run_locally.sh new file mode 100755 index 0000000000..c8e9a289f0 --- /dev/null +++ b/ci-scripts/run_locally.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +set -e +SHORT_COMMIT_SHA=$(git rev-parse --short=8 HEAD) +COMMIT_SHA=$(git rev-parse HEAD) +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) +REPO_PATH=$(dirname $(realpath $0))/../ +TESTCASE=$1 + +if [ $# -eq 0 ] + then + echo "Provide a testcase as an argument" + exit 1 +fi + +# The script assumes you've build the following images: +# +# docker build . -f docker/Dockerfile.gNB.ubuntu22 -t oai-gnb +# docker build . -f docker/Dockerfile.nrUE.ubuntu22 -t oai-nr-ue +# +# The images above depend on the following images: +# +# docker build . -f docker/Dockerfile.build.ubuntu22 -t ran-build +# dokcer build . -f docker/Dockerfile.base.ubuntu22 -t ran-base + +docker tag oai-nr-ue oai-ci/oai-nr-ue:develop-${SHORT_COMMIT_SHA} +docker tag oai-gnb oai-ci/oai-gnb:develop-${SHORT_COMMIT_SHA} + +python3 main.py --mode=TesteNB --ranRepository=NONE --ranBranch=${CURRENT_BRANCH} \ + --ranCommitID=${COMMIT_SHA} --ranAllowMerge=false \ + --ranTargetBranch=NONE --eNBIPAddress=NONE --eNBUserName=NONE --eNBPassword=NONE \ + --eNBSourceCodePath=NONE --EPCIPAddress=NONE --EPCType=OAI --eNBPassword=NONE \ + --eNBSourceCodePath=${REPO_PATH} --EPCIPAddress=NONE \ + --EPCUserName=NONE --EPCPassword=NONE --EPCSourceCodePath=NONE \ + --XMLTestFile=xml_files/${TESTCASE} --local