From e1798745ef70265baa7fd8809980e31725fc0dcb Mon Sep 17 00:00:00 2001
From: Robert Schmidt <robert.schmidt@openairinterface.org>
Date: Tue, 19 Jul 2022 18:21:28 +0200
Subject: [PATCH] Dynamically build L2sim proxy

---
 ci-scripts/Jenkinsfile-push-registry          |   2 +-
 ci-scripts/Jenkinsfile-tmp-multi-enb          | 187 +++++++++++++++++-
 ci-scripts/cls_containerize.py                | 119 +++++++++++
 ci-scripts/main.py                            |   7 +-
 ci-scripts/xml_class_list.yml                 |   1 +
 .../xml_files/container_5g_l2sim_proxy.xml    |  42 ++++
 6 files changed, 354 insertions(+), 4 deletions(-)
 create mode 100644 ci-scripts/xml_files/container_5g_l2sim_proxy.xml

diff --git a/ci-scripts/Jenkinsfile-push-registry b/ci-scripts/Jenkinsfile-push-registry
index bc1a80ba614..feb1807c912 100644
--- a/ci-scripts/Jenkinsfile-push-registry
+++ b/ci-scripts/Jenkinsfile-push-registry
@@ -66,7 +66,7 @@ pipeline {
           withCredentials([
             [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.DH_Credentials}", usernameVariable: 'DH_Username', passwordVariable: 'DH_Password']
           ]) {
-            def listOfImages = ["oai-enb", "oai-gnb", "oai-lte-ue", "oai-nr-ue"]
+            def listOfImages = ["oai-enb", "oai-gnb", "oai-lte-ue", "oai-nr-ue", "proxy"]
             sh "docker login -u ${DH_Username} -p ${DH_Password} > /dev/null 2>&1"
             listOfImages.eachWithIndex { item, iindex ->
               sh "docker image tag ${item}:develop ${DH_Account}/${item}:develop"
diff --git a/ci-scripts/Jenkinsfile-tmp-multi-enb b/ci-scripts/Jenkinsfile-tmp-multi-enb
index c3b825ec062..fb523169201 100644
--- a/ci-scripts/Jenkinsfile-tmp-multi-enb
+++ b/ci-scripts/Jenkinsfile-tmp-multi-enb
@@ -36,6 +36,9 @@ def testStageName = params.pipelineTestStageName
 // Name of the phone resource
 def ciSmartPhoneResource = params.smartphonesResource
 
+// Name of the phone resource
+def ciEpcResource = params.epcResource
+
 // Global Parameters. Normally they should be populated when the master job
 // triggers the slave job with parameters
 def eNB_Repository
@@ -51,7 +54,7 @@ pipeline {
     options {
         disableConcurrentBuilds()
         ansiColor('xterm')
-        lock (ciSmartPhoneResource)
+        lock(extra: [[resource: ciEpcResource]], resource: ciSmartPhoneResource)
     }
     stages {
         stage ("Verify Parameters") {
@@ -216,6 +219,76 @@ pipeline {
                 }
             }
         }
+        stage ("Terminate") {
+            parallel {
+                stage('Terminate UE') {
+                    // Bypassing this stage if there are no abd server defined
+                    when {
+                      expression { params.ADB_IPAddress != "none" }
+                    }
+                    steps {
+                        echo '\u2705 \u001B[32mTerminate UE\u001B[0m'
+                        withCredentials([
+                            [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.ADB_Credentials}", usernameVariable: 'ADB_Username', passwordVariable: 'ADB_Password']
+                        ]) {
+                            sh "python3 ci-scripts/main.py --mode=TerminateUE --ADBIPAddress=${params.ADB_IPAddress} --ADBUserName=${ADB_Username} --ADBPassword=${ADB_Password}"
+                        }
+                    }
+                }
+                stage('Terminate eNB') {
+                    steps {
+                        echo '\u2705 \u001B[32mTerminate eNB\u001B[0m'
+                        withCredentials([
+                            [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.eNB_Credentials}", usernameVariable: 'eNB_Username', passwordVariable: 'eNB_Password']
+                        ]) {
+                            sh "python3 ci-scripts/main.py --mode=TerminateeNB --eNBIPAddress=${params.eNB_IPAddress} --eNBUserName=${eNB_Username} --eNBPassword=${eNB_Password}"
+                        }
+                    }
+                }
+                stage('Terminate SPGW') {
+                    // Bypassing this stage if EPC server is not defined
+                    when {
+                      expression { params.EPC_IPAddress != "none" }
+                    }
+                    steps {
+                        echo '\u2705 \u001B[32mTerminate SPGW\u001B[0m'
+                        withCredentials([
+                            [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.EPC_Credentials}", usernameVariable: 'EPC_Username', passwordVariable: 'EPC_Password']
+                        ]) {
+                            sh "python3 ci-scripts/main.py --mode=TerminateSPGW --EPCIPAddress=${params.EPC_IPAddress} --EPCUserName=${EPC_Username} --EPCPassword=${EPC_Password} --EPCType=${params.EPC_Type} --EPCSourceCodePath=${params.EPC_SourceCodePath}"
+                        }
+                    }
+                }
+                stage('Terminate MME') {
+                    // Bypassing this stage if EPC server is not defined
+                    when {
+                      expression { params.EPC_IPAddress != "none" }
+                    }
+                    steps {
+                        echo '\u2705 \u001B[32mTerminate MME\u001B[0m'
+                        withCredentials([
+                            [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.EPC_Credentials}", usernameVariable: 'EPC_Username', passwordVariable: 'EPC_Password']
+                        ]) {
+                            sh "python3 ci-scripts/main.py --mode=TerminateMME --EPCIPAddress=${params.EPC_IPAddress} --EPCUserName=${EPC_Username} --EPCPassword=${EPC_Password} --EPCType=${params.EPC_Type} --EPCSourceCodePath=${params.EPC_SourceCodePath}"
+                        }
+                    }
+                }
+                stage('Terminate HSS') {
+                    // Bypassing this stage if EPC server is not defined
+                    when {
+                      expression { params.EPC_IPAddress != "none" }
+                    }
+                    steps {
+                        echo '\u2705 \u001B[32mTerminate HSS\u001B[0m'
+                        withCredentials([
+                            [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.EPC_Credentials}", usernameVariable: 'EPC_Username', passwordVariable: 'EPC_Password']
+                        ]) {
+                            sh "python3 ci-scripts/main.py --mode=TerminateHSS --EPCIPAddress=${params.EPC_IPAddress} --EPCUserName=${EPC_Username} --EPCPassword=${EPC_Password} --EPCType=${params.EPC_Type} --EPCSourceCodePath=${params.EPC_SourceCodePath}"
+                        }
+                    }
+                }
+            }
+        }
         stage('Log Collection') {
             parallel {
                 stage('Log Collection (eNB - Build)') {
@@ -242,7 +315,7 @@ pipeline {
                              [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.eNB_Credentials}", usernameVariable: 'eNB_Username', passwordVariable: 'eNB_Password']
                         ]) {
                             echo '\u2705 \u001B[32mLog Collection (eNB - Run)\u001B[0m'
-                            sh "python3 ci-scripts/main.py --mode=LogCollecteNB --eNBIPAddress=${params.eNB_IPAddress} --eNBUserName=${eNB_Username} --eNBPassword=${eNB_Password} --eNBSourceCodePath=${params.eNB_SourceCodePath}"
+                            sh "python3 ci-scripts/main.py --mode=LogCollecteNB --eNBIPAddress=${params.eNB_IPAddress} --eNBUserName=${eNB_Username} --eNBPassword=${eNB_Password} --eNBSourceCodePath=${params.eNB_SourceCodePath} --BuildId=${env.BUILD_ID}"
 
                             echo '\u2705 \u001B[32mLog Transfer (eNB - Run)\u001B[0m'
                             sh "sshpass -p \'${eNB_Password}\' scp -o 'StrictHostKeyChecking no' -o 'ConnectTimeout 10' ${eNB_Username}@${params.eNB_IPAddress}:${eNB_SourceCodePath}/cmake_targets/enb.log.zip ./enb.log.${env.BUILD_ID}.zip || true"
@@ -259,6 +332,116 @@ pipeline {
                         }
                     }
                 }
+                stage('Log Collection (SPGW)') {
+                    // Bypassing this stage if EPC server is not defined
+                    when {
+                      expression { params.EPC_IPAddress != "none" }
+                    }
+                    steps {
+                        withCredentials([
+                             [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.EPC_Credentials}", usernameVariable: 'EPC_Username', passwordVariable: 'EPC_Password']
+                        ]) {
+                            echo '\u2705 \u001B[32mLog Collection (SPGW)\u001B[0m'
+                            sh "python3 ci-scripts/main.py --mode=LogCollectSPGW --EPCIPAddress=${params.EPC_IPAddress} --EPCUserName=${EPC_Username} --EPCPassword=${EPC_Password} --EPCSourceCodePath=${params.EPC_SourceCodePath} --EPCType=${params.EPC_Type}"
+
+                            echo '\u2705 \u001B[32mLog Transfer (SPGW)\u001B[0m'
+                            sh "sshpass -p \'${EPC_Password}\' scp -o 'StrictHostKeyChecking no' -o 'ConnectTimeout 10' ${EPC_Username}@${params.EPC_IPAddress}:${EPC_SourceCodePath}/scripts/spgw.log.zip ./spgw.log.${env.BUILD_ID}.zip || true"
+                        }
+                        script {
+                            if(fileExists("spgw.log.${env.BUILD_ID}.zip")) {
+                                archiveArtifacts "spgw.log.${env.BUILD_ID}.zip"
+                            }
+                        }
+                    }
+                }
+                stage('Log Collection (MME)') {
+                    // Bypassing this stage if EPC server is not defined
+                    when {
+                      expression { params.EPC_IPAddress != "none" }
+                    }
+                    steps {
+                        withCredentials([
+                             [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.EPC_Credentials}", usernameVariable: 'EPC_Username', passwordVariable: 'EPC_Password']
+                        ]) {
+                            echo '\u2705 \u001B[32mLog Collection (MME)\u001B[0m'
+                            sh "python3 ci-scripts/main.py --mode=LogCollectMME --EPCIPAddress=${params.EPC_IPAddress} --EPCUserName=${EPC_Username} --EPCPassword=${EPC_Password} --EPCSourceCodePath=${params.EPC_SourceCodePath} --EPCType=${params.EPC_Type}"
+
+                            echo '\u2705 \u001B[32mLog Transfer (MME)\u001B[0m'
+                            sh "sshpass -p \'${EPC_Password}\' scp -o 'StrictHostKeyChecking no' -o 'ConnectTimeout 10' ${EPC_Username}@${params.EPC_IPAddress}:${EPC_SourceCodePath}/scripts/mme.log.zip ./mme.log.${env.BUILD_ID}.zip || true"
+                        }
+                        script {
+                            if(fileExists("mme.log.${env.BUILD_ID}.zip")) {
+                                archiveArtifacts "mme.log.${env.BUILD_ID}.zip"
+                            }
+                        }
+                    }
+                }
+                stage('Log Collection (HSS)') {
+                    // Bypassing this stage if EPC server is not defined
+                    when {
+                      expression { params.EPC_IPAddress != "none" }
+                    }
+                    steps {
+                        withCredentials([
+                             [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.EPC_Credentials}", usernameVariable: 'EPC_Username', passwordVariable: 'EPC_Password']
+                        ]) {
+                            echo '\u2705 \u001B[32mLog Collection (HSS)\u001B[0m'
+                            sh "python3 ci-scripts/main.py --mode=LogCollectHSS --EPCIPAddress=${params.EPC_IPAddress} --EPCUserName=${EPC_Username} --EPCPassword=${EPC_Password} --EPCSourceCodePath=${params.EPC_SourceCodePath} --EPCType=${params.EPC_Type}"
+
+                            echo '\u2705 \u001B[32mLog Transfer (HSS)\u001B[0m'
+                            sh "sshpass -p \'${EPC_Password}\' scp -o 'StrictHostKeyChecking no' -o 'ConnectTimeout 10' ${EPC_Username}@${params.EPC_IPAddress}:${EPC_SourceCodePath}/scripts/hss.log.zip ./hss.log.${env.BUILD_ID}.zip || true"
+                        }
+                        script {
+                            if(fileExists("hss.log.${env.BUILD_ID}.zip")) {
+                                archiveArtifacts "hss.log.${env.BUILD_ID}.zip"
+                            }
+                        }
+                    }
+                }
+                stage('Log Collection (Ping)') {
+                    // Bypassing this stage if EPC server is not defined
+                    when {
+                      expression { params.EPC_IPAddress != "none" }
+                    }
+                    steps {
+                        withCredentials([
+                             [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.EPC_Credentials}", usernameVariable: 'EPC_Username', passwordVariable: 'EPC_Password']
+                        ]) {
+                            echo '\u2705 \u001B[32mLog Collection (Ping)\u001B[0m'
+                            sh "python3 ci-scripts/main.py --mode=LogCollectPing --EPCIPAddress=${params.EPC_IPAddress} --EPCUserName=${EPC_Username} --EPCPassword=${EPC_Password} --EPCSourceCodePath=${params.EPC_SourceCodePath} --EPCType=${params.EPC_Type}"
+
+                            echo '\u2705 \u001B[32mLog Transfer (Ping)\u001B[0m'
+                            sh "sshpass -p \'${EPC_Password}\' scp -o 'StrictHostKeyChecking no' -o 'ConnectTimeout 10' ${EPC_Username}@${params.EPC_IPAddress}:${EPC_SourceCodePath}/scripts/ping.log.zip ./ping.log.${env.BUILD_ID}.zip || true"
+                        }
+                        script {
+                            if(fileExists("ping.log.${env.BUILD_ID}.zip")) {
+                                archiveArtifacts "ping.log.${env.BUILD_ID}.zip"
+                            }
+                        }
+                    }
+                }
+                stage('Log Collection (Iperf)') {
+                    // Bypassing this stage if EPC server is not defined
+                    when {
+                      expression { params.EPC_IPAddress != "none" }
+                    }
+                    steps {
+                        withCredentials([
+                             [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.EPC_Credentials}", usernameVariable: 'EPC_Username', passwordVariable: 'EPC_Password']
+                        ]) {
+                            echo '\u2705 \u001B[32mLog Collection (Iperf)\u001B[0m'
+                            sh "python3 ci-scripts/main.py --mode=LogCollectIperf --EPCIPAddress=${params.EPC_IPAddress} --EPCUserName=${EPC_Username} --EPCPassword=${EPC_Password} --EPCSourceCodePath=${params.EPC_SourceCodePath} --EPCType=${params.EPC_Type}"
+
+                            echo '\u2705 \u001B[32mLog Transfer (Iperf)\u001B[0m'
+                            sh "sshpass -p \'${EPC_Password}\' scp -o 'StrictHostKeyChecking no' -o 'ConnectTimeout 10' ${EPC_Username}@${params.EPC_IPAddress}:${EPC_SourceCodePath}/scripts/iperf.log.zip ./iperf.log.${env.BUILD_ID}.zip || true"
+                        }
+                        script {
+                            if(fileExists("iperf.log.${env.BUILD_ID}.zip")) {
+                                archiveArtifacts "iperf.log.${env.BUILD_ID}.zip"
+                            }
+                        }
+                    }
+                }
             }
         }
     }
diff --git a/ci-scripts/cls_containerize.py b/ci-scripts/cls_containerize.py
index 6abf444a0dc..8f992689e20 100644
--- a/ci-scripts/cls_containerize.py
+++ b/ci-scripts/cls_containerize.py
@@ -74,6 +74,7 @@ class Containerize():
 		self.eNB2SourceCodePath = ''
 		self.forcedWorkspaceCleanup = False
 		self.imageKind = ''
+		self.proxyCommit = None
 		self.eNB_instance = 0
 		self.eNB_serverId = ['', '', '']
 		self.yamlPath = ['', '', '']
@@ -421,6 +422,124 @@ class Containerize():
 			HTML.CreateHtmlTabFooter(False)
 			sys.exit(1)
 
+	def BuildProxy(self, HTML):
+		if self.ranRepository == '' or self.ranBranch == '' or self.ranCommitID == '':
+			HELP.GenericHelp(CONST.Version)
+			sys.exit('Insufficient Parameter')
+		if self.eNB_serverId[self.eNB_instance] == '0':
+			lIpAddr = self.eNBIPAddress
+			lUserName = self.eNBUserName
+			lPassWord = self.eNBPassword
+			lSourcePath = self.eNBSourceCodePath
+		elif self.eNB_serverId[self.eNB_instance] == '1':
+			lIpAddr = self.eNB1IPAddress
+			lUserName = self.eNB1UserName
+			lPassWord = self.eNB1Password
+			lSourcePath = self.eNB1SourceCodePath
+		elif self.eNB_serverId[self.eNB_instance] == '2':
+			lIpAddr = self.eNB2IPAddress
+			lUserName = self.eNB2UserName
+			lPassWord = self.eNB2Password
+			lSourcePath = self.eNB2SourceCodePath
+		if lIpAddr == '' or lUserName == '' or lPassWord == '' or lSourcePath == '':
+			HELP.GenericHelp(CONST.Version)
+			sys.exit('Insufficient Parameter')
+		if self.proxyCommit is None:
+			HELP.GenericHelp(CONST.Version)
+			sys.exit('Insufficient Parameter (need proxyCommit for proxy build)')
+		logging.debug('Building on server: ' + lIpAddr)
+		mySSH = SSH.SSHConnection()
+		mySSH.open(lIpAddr, lUserName, lPassWord)
+
+		# Check that we are on Ubuntu
+		mySSH.command('hostnamectl', '\$', 5)
+		result = re.search('Ubuntu',  mySSH.getBefore())
+		self.host = result.group(0)
+		if self.host != 'Ubuntu':
+			logging.error('\u001B[1m Can build proxy only on Ubuntu server\u001B[0m')
+			mySSH.close()
+			sys.exit(1)
+
+		self.cli = 'docker'
+		self.cliBuildOptions = '--no-cache'
+
+		# Workaround for some servers, we need to erase completely the workspace
+		if self.forcedWorkspaceCleanup:
+			mySSH.command('echo ' + lPassWord + ' | sudo -S rm -Rf ' + lSourcePath, '\$', 15)
+
+		oldRanCommidID = self.ranCommitID
+		oldRanRepository = self.ranRepository
+		oldRanAllowMerge = self.ranAllowMerge
+		self.ranCommitID = self.proxyCommit
+		self.ranRepository = 'https://github.com/EpiSci/oai-lte-5g-multi-ue-proxy.git'
+		self.ranAllowMerge = False
+		self._createWorkspace(mySSH, lPassWord, lSourcePath)
+		# to prevent accidentally overwriting data that might be used later
+		self.ranCommitID = oldRanCommidID
+		self.ranRepository = oldRanRepository
+		self.ranAllowMerge = oldRanAllowMerge
+
+		# Let's remove any previous run artifacts if still there
+		mySSH.command(self.cli + ' image prune --force', '\$', 30)
+		# Remove any previous proxy image
+		mySSH.command(self.cli + ' image rm oai-lte-multi-ue-proxy:latest || true', '\$', 30)
+
+		tag = self.proxyCommit
+		logging.debug('building L2sim proxy image for tag ' + tag)
+		# check if the corresponding proxy image with tag exists. If not, build it
+		mySSH.command(self.cli + ' image inspect --format=\'Size = {{.Size}} bytes\' proxy:' + tag, '\$', 5)
+		buildProxy = mySSH.getBefore().count('o such image') != 0
+		if buildProxy:
+			mySSH.command(self.cli + ' build ' + self.cliBuildOptions + ' --target oai-lte-multi-ue-proxy --tag proxy:' + tag + ' --file docker/Dockerfile.ubuntu18.04 . > cmake_targets/log/proxy-build.log 2>&1', '\$', 180)
+			# Note: at this point, OAI images are flattened, but we cannot do this
+			# here, as the flatten script is not in the proxy repo
+			mySSH.command(self.cli + ' image inspect --format=\'Size = {{.Size}} bytes\' proxy:' + tag, '\$', 5)
+			if mySSH.getBefore().count('o such image') != 0:
+				logging.error('\u001B[1m Build of L2sim proxy failed\u001B[0m')
+				mySSH.close()
+				HTML.CreateHtmlTestRow('commit ' + tag, 'KO', CONST.ALL_PROCESSES_OK)
+				HTML.CreateHtmlTabFooter(False)
+				sys.exit(1)
+		else:
+			logging.debug('L2sim proxy image for tag ' + tag + ' already exists, skipping build')
+
+		# retag the build images to that we pick it up later
+		mySSH.command('docker image tag proxy:' + tag + ' oai-lte-multi-ue-proxy:latest', '\$', 5)
+
+		# no merge: is a push to develop, tag the image so we can push it to the registry
+		if not self.ranAllowMerge:
+			mySSH.command('docker image tag proxy:' + tag + ' proxy:develop', '\$', 5)
+
+		# we assume that the host on which this is built will also run the proxy. The proxy
+		# currently requires the following command, and the docker-compose up mechanism of
+		# the CI does not allow to run arbitrary commands. Note that the following actually
+		# belongs to the deployment, not the build of the proxy...
+		logging.warning('the following command belongs to deployment, but no mechanism exists to exec it there!')
+		mySSH.command('sudo ifconfig lo: 127.0.0.2 netmask 255.0.0.0 up', '\$', 5)
+
+		# Analyzing the logs
+		if buildProxy:
+			self.testCase_id = HTML.testCase_id
+			mySSH.command('cd ' + lSourcePath + '/cmake_targets', '\$', 5)
+			mySSH.command('mkdir -p proxy_build_log_' + self.testCase_id, '\$', 5)
+			mySSH.command('mv log/* ' + 'proxy_build_log_' + self.testCase_id, '\$', 5)
+			if (os.path.isfile('./proxy_build_log_' + self.testCase_id + '.zip')):
+				os.remove('./proxy_build_log_' + self.testCase_id + '.zip')
+			if (os.path.isdir('./proxy_build_log_' + self.testCase_id)):
+				shutil.rmtree('./proxy_build_log_' + self.testCase_id)
+			mySSH.command('zip -r -qq proxy_build_log_' + self.testCase_id + '.zip proxy_build_log_' + self.testCase_id, '\$', 5)
+			mySSH.copyin(lIpAddr, lUserName, lPassWord, lSourcePath + '/cmake_targets/build_log_' + self.testCase_id + '.zip', '.')
+			# don't delete such that we might recover the zips
+			#mySSH.command('rm -f build_log_' + self.testCase_id + '.zip','\$', 5)
+
+		# Cleaning any created tmp volume
+		mySSH.command(self.cli + ' volume prune --force || true','\$', 15)
+		mySSH.close()
+
+		logging.info('\u001B[1m Building L2sim Proxy Image Pass\u001B[0m')
+		HTML.CreateHtmlTestRow('commit ' + tag, 'OK', CONST.ALL_PROCESSES_OK)
+		HTML.CreateHtmlNextTabHeaderTestRow(self.collectInfo, self.allImagesSize)
+
 	def Copy_Image_to_Test_Server(self, HTML):
 		imageTag = 'develop'
 		if (self.ranAllowMerge):
diff --git a/ci-scripts/main.py b/ci-scripts/main.py
index 448f2636e34..baed6a60119 100644
--- a/ci-scripts/main.py
+++ b/ci-scripts/main.py
@@ -102,7 +102,7 @@ def AssignParams(params_dict):
 
 
 def GetParametersFromXML(action):
-	if action == 'Build_eNB' or action == 'Build_Image':
+	if action == 'Build_eNB' or action == 'Build_Image' or action == 'Build_Proxy':
 		RAN.Build_eNB_args=test.findtext('Build_eNB_args')
 		CONTAINERS.imageKind=test.findtext('kind')
 		forced_workspace_cleanup = test.findtext('forced_workspace_cleanup')
@@ -138,6 +138,9 @@ def GetParametersFromXML(action):
 				RAN.backgroundBuild=True
 			else:
 				RAN.backgroundBuild=False
+		proxy_commit = test.findtext('proxy_commit')
+		if proxy_commit is not None:
+			CONTAINERS.proxyCommit = proxy_commit
 
 	elif action == 'WaitEndBuild_eNB':
 		RAN.Build_eNB_args=test.findtext('Build_eNB_args')
@@ -915,6 +918,8 @@ elif re.match('^TesteNB$', mode, re.IGNORECASE) or re.match('^TestUE$', mode, re
 					HTML=ldpc.Run_PhySim(HTML,CONST,id)
 				elif action == 'Build_Image':
 					CONTAINERS.BuildImage(HTML)
+				elif action == 'Build_Proxy':
+					CONTAINERS.BuildProxy(HTML)
 				elif action == 'Copy_Image_to_Test':
 					CONTAINERS.Copy_Image_to_Test_Server(HTML)
 				elif action == 'Deploy_Object':
diff --git a/ci-scripts/xml_class_list.yml b/ci-scripts/xml_class_list.yml
index b305aff4243..2c5f04b11fa 100755
--- a/ci-scripts/xml_class_list.yml
+++ b/ci-scripts/xml_class_list.yml
@@ -1,3 +1,4 @@
+  - Build_Proxy
   - Build_PhySim
   - Run_PhySim
   - Build_eNB
diff --git a/ci-scripts/xml_files/container_5g_l2sim_proxy.xml b/ci-scripts/xml_files/container_5g_l2sim_proxy.xml
new file mode 100644
index 00000000000..af1715d7936
--- /dev/null
+++ b/ci-scripts/xml_files/container_5g_l2sim_proxy.xml
@@ -0,0 +1,42 @@
+<!--
+
+ Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The OpenAirInterface Software Alliance licenses this file to You under
+ the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.openairinterface.org/?page_id=698
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ For more information about the OpenAirInterface (OAI) Software Alliance:
+      contact@openairinterface.org
+
+-->
+<testCaseList>
+        <htmlTabRef>l2sim-5gnr-proxy-build</htmlTabRef>
+        <htmlTabName>Build L2sim proxy image</htmlTabName>
+        <htmlTabIcon>wrench</htmlTabIcon>
+        <repeatCount>1</repeatCount>
+        <TestCaseRequestedList>
+ 000001
+        </TestCaseRequestedList>
+        <TestCaseExclusionList></TestCaseExclusionList>
+
+        <testCase id="000001">
+                <class>Build_Proxy</class>
+                <desc>Build L2sim Proxy Image</desc>
+                <eNB_instance>1</eNB_instance>
+                <eNB_serverId>1</eNB_serverId>
+                <forced_workspace_cleanup>True</forced_workspace_cleanup>
+                <proxy_commit>56cfdc0</proxy_commit>
+        </testCase>
+
+</testCaseList>
-- 
GitLab