From ec6362733401bbdb971275b3039c74628db23d87 Mon Sep 17 00:00:00 2001
From: ismail <mohammed.ismail@openairinterface.org>
Date: Wed, 12 May 2021 13:59:42 +0200
Subject: [PATCH] CI: initial ci draft

Signed-off-by: ismail <mohammed.ismail@openairinterface.org>
---
 ci-scripts/Jenkinsfile-GitLab-Helm        | 275 +++++++++++++++++++++
 ci-scripts/helmDeploy.py                  | 279 ++++++++++++++++++++++
 ci-scripts/sshconnection.py               | 259 ++++++++++++++++++++
 openshift/oai-amf-image-stream.yml        |  30 +++
 openshift/oai-nrf-image-stream.yml        |  30 +++
 openshift/oai-smf-image-stream.yml        |  30 +++
 openshift/oai-spgwu-tiny-image-stream.yml |  30 +++
 7 files changed, 933 insertions(+)
 create mode 100644 ci-scripts/Jenkinsfile-GitLab-Helm
 create mode 100644 ci-scripts/helmDeploy.py
 create mode 100644 ci-scripts/sshconnection.py
 create mode 100644 openshift/oai-amf-image-stream.yml
 create mode 100644 openshift/oai-nrf-image-stream.yml
 create mode 100644 openshift/oai-smf-image-stream.yml
 create mode 100644 openshift/oai-spgwu-tiny-image-stream.yml

diff --git a/ci-scripts/Jenkinsfile-GitLab-Helm b/ci-scripts/Jenkinsfile-GitLab-Helm
new file mode 100644
index 00000000..74ad3f2a
--- /dev/null
+++ b/ci-scripts/Jenkinsfile-GitLab-Helm
@@ -0,0 +1,275 @@
+#!/bin/groovy
+/*
+ * 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
+ */
+
+//-------------------------------------------------------------------------------
+// Abstraction function to send social media messages:
+// like on Slack or Mattermost
+def sendSocialMediaMessage(pipeChannel, pipeColor, pipeMessage) {
+  if (params.pipelineUsesSlack != null) {
+    if (params.pipelineUsesSlack) {
+      slackSend channel: pipeChannel, color: pipeColor, message: pipeMessage
+    }
+  }
+}
+
+// Location of the CN executor node
+def cn_ci_host = params.Host_CN_CI_Server
+
+// for lock
+def ds_tester_ci_resource = params.DsTester
+
+// Location of the DsTester workspace
+def dsTestFrameworkLocation = params.dsTestFrameworkLocation
+
+// When triggered by upstream, specify which tag to use
+def upstreamTagToUse = params.upstreamTagToUse
+
+// Location of the CN tester
+def dsT_host_flag = false
+def dsT_host = ""
+def dsT_host_user = ""
+def dsT_host_ip_addr = ""
+
+// Flags
+def scmEvent = false
+def upstreamEvent = false
+
+// Default tags  --> could be passed on by upstream job or by PR content
+def nrfTag = params.nrfTag
+def amfTag = params.amfTag
+def smfTag = params.smfTag
+def spgwuTag = params.spgwuTag
+
+//-------------------------------------------------------------------------------
+// Pipeline start
+pipeline {
+  agent {
+    label cn_ci_host
+  }
+  options {
+    disableConcurrentBuilds()
+    timestamps()
+    ansiColor('xterm')
+    lock(cn_ci_resource)
+  }
+  stages {
+    stage ('Verify Parameters') {
+      steps {
+        script {
+          echo '\u2705 \u001B[32mVerify Parameters\u001B[0m'
+
+          JOB_TIMESTAMP = sh returnStdout: true, script: 'date --utc --rfc-3339=seconds | sed -e "s#+00:00##"'
+          JOB_TIMESTAMP = JOB_TIMESTAMP.trim()
+
+          def allParametersPresent = true
+          if (params.eNB_IPAddress == null) {
+            allParametersPresent = false
+          if (params.eNB_Credentials == null) {
+            allParametersPresent = false
+          if (params.OC_Credentials == null) {
+            allParametersPresent = false
+          }
+          if (params.OC_ProjectName == null) {
+            allParametersPresent = false
+          }
+          if (allParametersPresent) {
+            echo "Cluster Access parameters are present"
+          } else {
+            echo "Some Cluster Access parameters are missing"
+            sh "./ci-scripts/fail.sh"
+          }
+          if (params.DS_Tester_Server_Flag != null) {
+            dsT_host_flag = params.DS_Tester_Server_Flag
+            if (dsT_host_flag) {
+              def allParametersPresent = true
+              if (params.DS_Tester_Server_Name == null) {
+                allParametersPresent = false
+              } else {
+                dsT_host = params.DS_Tester_Server_Name
+              }
+              if (params.DS_Tester_Server_Login == null) {
+                allParametersPresent = false
+              } else {
+                dsT_host_user = params.DS_Tester_Server_Login
+              }
+              if (params.DS_Tester_Server_IP_Addr == null) {
+                allParametersPresent = false
+              } else {
+                dsT_host_ip_addr = params.DS_Tester_Server_IP_Addr
+              }
+              if (allParametersPresent) {
+                echo "DS Tester  is on ${dsT_host}"
+              } else {
+                echo "Some DS Tester parameters are missing!"
+                sh "./ci-scripts/fail.sh"
+              }
+            }
+          }
+
+          // Find out the cause of the trigger
+          for (cause in currentBuild.getBuildCauses()) {
+            if (cause.toString() ==~ /.*UpstreamCause.*/) {
+              upstreamEvent = true
+            //} else {
+            //  scmEvent = true
+            }
+          }
+          withCredentials([
+            [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.eNB_Credentials}", usernameVariable: 'eNB_Username', passwordVariable: 'eNB_Password'],
+            [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.OC_Credentials}", usernameVariable: 'OC_Username', passwordVariable: 'OC_Password']
+          ]) {
+            if (upstreamEvent) {
+              if (params.NRF_TAG != null) {
+                nrfTag = params.NRF_TAG
+                echo "Upstream Job passed NRF_TAG to use: ${nrfTag}"
+              }
+              if (params.AMF_TAG != null) {
+                amfTag = params.AMF_TAG
+                echo "Upstream Job passed AMF_TAG to use: ${amfTag}"
+              }
+              if (params.SMF_TAG != null) {
+                smfTag = params.SMF_TAG
+                echo "Upstream Job passed SMF_TAG to use: ${smfTag}"
+              }
+              sh "git clean -x -d -f > /dev/null 2>&1"
+              sh "git fetch --prune > /dev/null 2>&1"
+              sh 'git checkout -f ' + upstreamTagToUse
+              sh "zip -r -qq oai-cn5g-fed.zip .git"
+              sh "mkdir -p archives DS-TEST-RESULTS"
+              // Prepare the workspace in the remote server 
+              copyTo2ndServer('oai-cn5g-fed.zip', true, ${eNB_Username}, ${params.eNB_IPAddress})
+              myShCmd('git clean -x -d -f > /dev/null 2>&1', true, ${eNB_Username}, ${params.eNB_IPAddress})
+              myShCmd('mkdir -p archives DS-TEST-RESULTS', true, ${eNB_Username}, ${params.eNB_IPAddress})
+            }
+            if (scmEvent) {
+              sh "git clean -x -d -f > /dev/null 2>&1"
+              if ("MERGE".equals(env.gitlabActionType)) {
+                sh "./ci-scripts/doGitLabMerge.sh --src-branch ${env.gitlabSourceBranch} --src-commit ${env.gitlabMergeRequestLastCommit} --target-branch ${env.gitlabTargetBranch} --target-commit ${GIT_COMMIT}"
+              }
+              sh "zip -r -qq oai-cn5g-fed.zip .git"
+              sh "mkdir -p archives DS-TEST-RESULTS"
+              // Prepare the workspace in remote server
+              copyTo2ndServer('oai-cn5g-fed.zip', true, ${eNB_Username}, ${params.eNB_IPAddress})
+              myShCmd('git clean -x -d -f > /dev/null 2>&1', true, ${eNB_Username}, ${params.eNB_IPAddress})
+              if ("MERGE".equals(env.gitlabActionType)) {
+                myShCmd("./ci-scripts/doGitLabMerge.sh --src-branch ${env.gitlabSourceBranch} --src-commit ${env.gitlabMergeRequestLastCommit} --target-branch ${env.gitlabTargetBranch} --target-commit ${GIT_COMMIT}", new_host_flag, new_host_user, new_host)
+              }
+              myShCmd('mkdir -p archives DS-TEST-RESULTS', true, ${eNB_Username}, ${params.eNB_IPAddress})
+            }
+            if ((!upstreamEvent) && (!scmEvent)) {
+              sh "git clean -x -d -f > /dev/null 2>&1"
+              sh "zip -r -qq oai-cn5g-fed.zip .git"
+              sh "mkdir -p archives DS-TEST-RESULTS"
+              // Prepare the workspace in the remote server 
+              copyTo2ndServer('oai-cn5g-fed.zip', true, ${eNB_Username}, ${params.eNB_IPAddress})
+              myShCmd('git clean -x -d -f > /dev/null 2>&1', true, ${eNB_Username}, ${params.eNB_IPAddress})
+              myShCmd('mkdir -p archives DS-TEST-RESULTS', true, ${eNB_Username}, ${params.eNB_IPAddress})
+            }
+            imageTags = "oai-nrf:${nrfTag},oai-amf:${amfTag},oai-smf:${smfTag},oai-spgwu-tiny:${spgwuTag}"
+          }
+        }
+      }
+    }
+    stage ('Deploy Whole 5G Core Network') {
+      steps {
+        script {
+          echo '\u2705 \u001B[32mDeploy CN5G in idle mode\u001B[0m'
+          withCredentials([
+            [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.eNB_Credentials}", usernameVariable: 'eNB_Username', passwordVariable: 'eNB_Password'],
+            [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.OC_Credentials}", usernameVariable: 'OC_Username', passwordVariable: 'OC_Password']
+          ]) {
+            try {
+              sh "python3 helmDeploy.py --mode=Deploy --eNBIPAddress=${params.eNB_IPAddress} --eNBUserName=${eNB_Username} --eNBPassword=${eNB_Password} --OCUserName=${OC_Username} --OCPassword=${OC_Password} --OCProjectName=${OC_ProjectName} --imageTags=${imageTags}"
+            } catch (Exception e) {
+              currentBuild.result = 'FAILURE'
+            }
+          }
+        }
+      }
+    }
+    stage ('Undeploy 5G-CN') {
+      steps {
+        script {
+          withCredentials([
+            [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.eNB_Credentials}", usernameVariable: 'eNB_Username', passwordVariable: 'eNB_Password'],
+            [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.OC_Credentials}", usernameVariable: 'OC_Username', passwordVariable: 'OC_Password']
+          ]) {
+            try {
+              sh "python3 helmDeploy.py --mode=UnDeploy --eNBIPAddress=${params.eNB_IPAddress} --eNBUserName=${eNB_Username} --eNBPassword=${eNB_Password} --OCUserName=${OC_Username} --OCPassword=${OC_Password} --OCProjectName=${OC_ProjectName} --imageTags=${imageTags}"
+            } catch (Exception e) {
+              currentBuild.result = 'FAILURE'
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+// Functions
+
+def copyTo2ndServer(filename, flag, user, host) {
+  if (flag) {
+    if ("oai-cn5g-fed.zip".equals(filename)) {
+      sh "ssh ${user}@${host} 'rm -rf /tmp/CI-CN5G-FED-RHEL8'"
+      sh "ssh ${user}@${host} 'mkdir -p /tmp/CI-CN5G-FED-RHEL8'"
+    }
+    sh "scp ${filename} ${user}@${host}:/tmp/CI-CN5G-FED-RHEL8"
+    if ("oai-cn5g-fed.zip".equals(filename)) {
+      sh "ssh ${user}@${host} 'cd /tmp/CI-CN5G-FED-RHEL8 && unzip -qq oai-cn5g-fed.zip && rm oai-cn5g-fed.zip'"
+      sh "ssh ${user}@${host} 'cd /tmp/CI-CN5G-FED-RHEL8 && git checkout -f ${GIT_COMMIT}'"
+      sh "ssh ${user}@${host} 'cd /tmp/CI-CN5G-FED-RHEL8 && git log -n1'"
+    }
+  }
+}
+
+def copyFrom2ndServer(filename, target, flag, user, host) {
+  if (flag) {
+    sh "scp ${user}@${host}:/tmp/CI-CN5G-FED-RHEL8/${filename} ${target}"
+  }
+}
+
+def myShCmd(cmd, flag, user, host) {
+  if (flag) {
+    sh "ssh -t -t ${user}@${host} 'cd /tmp/CI-CN5G-FED-RHEL8 && ${cmd}'"
+  } else {
+    sh "${cmd}"
+  }
+}
+
+def myShCmdWithLog(cmd, logFile, flag, user, host) {
+  if (flag) {
+    sh "ssh -t -t ${user}@${host} 'export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:.:/usr/local/devsol/bin && ${cmd}' > ${logFile} 2>&1"
+  } else {
+    sh "${cmd} > ${logFile} 2>&1"
+  }
+}
+
+def myShRetCmd(cmd, flag, user, host) {
+  if (flag) {
+    ret = sh returnStdout: true, script: "ssh -t -t ${user}@${host} 'cd /tmp/CI-CN5G-FED-RHEL8 && ${cmd}'"
+  } else {
+    ret = sh returnStdout: true, script: "${cmd}"
+  }
+  ret = ret.trim()
+  return ret
+}
diff --git a/ci-scripts/helmDeploy.py b/ci-scripts/helmDeploy.py
new file mode 100644
index 00000000..431cadca
--- /dev/null
+++ b/ci-scripts/helmDeploy.py
@@ -0,0 +1,279 @@
+#/*
+# * 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
+# */
+#---------------------------------------------------------------------
+#
+#   Required Python Version
+#     Python 3.x
+#
+#   Required Python Package
+#     pexpect
+#---------------------------------------------------------------------
+
+#-----------------------------------------------------------
+# Import
+#-----------------------------------------------------------
+import logging
+import sshconnection as SSH
+import html
+import os
+import re
+import time
+import sys
+
+class ClusterDeploy:
+	def __init__(self):
+		self.eNBIPAddress = ""
+		self.eNBUserName = ""
+		self.eNBPassword = ""
+		self.OCUserName = ""
+		self.OCPassword = ""
+		self.OCProjectName = ""
+		self.sourceCodePath = "/tmp/CI-CN5G-FED-RHEL8"
+        self.imageTags = ""
+		self.mode = ""
+
+#-----------------$
+#PUBLIC Methods$
+#-----------------$
+
+	def Deploy_5gcn(self):
+		lIpAddr = self.eNBIPAddress
+		lUserName = self.eNBUserName
+		lPassWord = self.eNBPassword
+		lSourcePath = self.sourceCodePath
+		ocUserName = self.OCUserName
+		ocPassword = self.OCPassword
+		ocProjectName = self.OCProjectName
+        limageTags = self.imageTags
+		if lIpAddr == '' or lUserName == '' or lPassWord == '' or lSourcePath == '' or ocUserName == '' or ocPassword == '' or ocProjectName == '' or limageTags == '':
+			sys.exit('Insufficient Parameter')
+		logging.debug('Running on server: ' + lIpAddr)
+		mySSH = SSH.SSHConnection()
+		mySSH.open(lIpAddr, lUserName, lPassWord)
+		mySSH.command('cd ' + lSourcePath, '\$', 5)
+
+        images = limageTags.split(',')
+        for image in images:
+            eachImage = image.split(':')
+            imageName = eachImage(0)
+            imageTag = eachImage(1)
+            # Check if image is exist on the Red Hat server, before pushing it to OC cluster
+            mySSH.command("sudo podman image inspect --format='Size = {{.Size}} bytes' " + imageName + ":" + imageTag, '\$', 60)
+            if mySSH.getBefore().count('no such image') != 0:
+                logging.error(f'\u001B[1m No such image {imageName}]\u001B[0m')
+                mySSH.close()
+                sys.exit(-1)
+            else:
+                result = re.search('Size *= *(?P<size>[0-9\-]+) *bytes', mySSH.getBefore())
+                if result is not None:
+                    imageSize = float(result.group('size'))
+                    imageSize = imageSize / 1000
+                    if imageSize < 1000:
+                        logging.debug(f'\u001B[1m   {imageName} size is ' + ('%.0f' % imageSize) + ' kbytes\u001B[0m')
+                    else:
+                        imageSize = imageSize / 1000
+                        if imageSize < 1000:
+                            logging.debug(f'\u001B[1m   {imageName} size is ' + ('%.0f' % imageSize) + ' Mbytes\u001B[0m')
+                        else:
+                            imageSize = imageSize / 1000
+                            logging.debug(f'\u001B[1m   {imageName} is ' + ('%.3f' % imageSize) + ' Gbytes\u001B[0m')
+                else:
+                    logging.debug(f'{imageName} size is unknown')
+
+		# logging to OC Cluster and then switch to corresponding project
+		mySSH.command(f'oc login -u {ocUserName} -p {ocPassword}', '\$', 6)
+		if mySSH.getBefore().count('Login successful.') == 0:
+			logging.error('\u001B[1m OC Cluster Login Failed\u001B[0m')
+			mySSH.close()
+			sys.exit(-1)
+		else:
+			logging.debug('\u001B[1m   Login to OC Cluster Successfully\u001B[0m')
+		mySSH.command(f'oc project {ocProjectName}', '\$', 6)
+		if mySSH.getBefore().count(f'Already on project "{ocProjectName}"') == 0 and mySSH.getBefore().count(f'Now using project "{self.OCProjectName}"') == 0:
+			logging.error(f'\u001B[1m Unable to access OC project {ocProjectName}\u001B[0m')
+			mySSH.close()
+			sys.exit(-1)
+		else:
+			logging.debug(f'\u001B[1m   Now using project {ocProjectName}\u001B[0m')
+
+		# Tag the image and push to the OC cluster
+		mySSH.command('oc whoami -t | sudo podman login -u ' + ocUserName + ' --password-stdin https://default-route-openshift-image-registry.apps.5glab.nsa.eurecom.fr/ --tls-verify=false', '\$', 6)
+		if mySSH.getBefore().count('Login Succeeded!') == 0:
+			logging.error('\u001B[1m Podman Login to OC Cluster Registry Failed\u001B[0m')
+			mySSH.close()
+			sys.exit(-1)
+		else:
+			logging.debug('\u001B[1m Podman Login to OC Cluster Registry Successfully\u001B[0m')
+        for image in images:
+            imageName = image(0)
+            imageTag = image(1)
+            mySSH.command(f'oc create -f openshift/{imageName}-image-stream.yml', '\$', 6)
+            if mySSH.getBefore().count('(AlreadyExists):') == 0 and mySSH.getBefore().count('created') == 0:
+                logging.error(f'\u001B[1m Image Stream "{imageName}" Creation Failed on OC Cluster {ocProjectName}\u001B[0m')
+                mySSH.close()
+                sys.exit(-1)
+            else:
+                logging.debug(f'\u001B[1m   Image Stream "{imageName}" created on OC project {ocProjectName}\u001B[0m')
+            mySSH.command(f'sudo podman tag {imageName}:{imageTag} default-route-openshift-image-registry.apps.5glab.nsa.eurecom.fr/{self.OCProjectName}/{imageName}:{imageTag}', '\$', 6)
+            mySSH.command(f'sudo podman push default-route-openshift-image-registry.apps.5glab.nsa.eurecom.fr/{self.OCProjectName}/{imageName}:{imageTag} --tls-verify=false', '\$', 60)
+            if mySSH.getBefore().count('Storing signatures') == 0:
+                logging.error(f'\u001B[1m Image "{imageName}" push to OC Cluster Registry Failed\u001B[0m')
+                mySSH.close()
+                sys.exit(-1)
+            else:
+                logging.debug(f'\u001B[1m Image "{imageName}" push to OC Cluster Registry Successfully\u001B[0m')
+
+		passPods = 0
+		# Using helm charts deployment
+		time.sleep(5)
+        for image in images:
+            eachImage = image.split(':')
+            imageName = eachImage(0)
+            imageTag = eachImage(1)		
+			mySSH.command(f'sed -i -e "s#TAG#{imageTag}#g" ./charts/oai-5gcn/charts/{imageName}/values.yaml', '\$', 6)
+			if imageName == 'oai-nrf':
+				nameSufix = 'nrf'
+			elif imageName == 'oai-amf':
+				nameSufix = 'amf'
+			elif imageName == 'oai-smf':
+				nameSufix = 'smf'
+			elif imageName == 'oai-spgwu-tiny':
+				nameSufix = 'spgwu'
+			mySSH.command(f'helm install {imageName} ./charts/{imageName}/ | tee -a archives/5gcn_helm_summary.txt 2>&1', '\$', 6)
+			if mySSH.getBefore().count('STATUS: deployed') == 0:
+				logging.error(f'\u001B[1m Deploying "{imageName}" Failed using helm chart on OC Cluster\u001B[0m')
+			else:
+				logging.debug(f'\u001B[1m   Deployed "{imageName}" Successfully using helm chart\u001B[0m')
+			time.sleep(60)
+			mySSH.command(f'oc get pods -o wide -l app.kubernetes.io/name={imageName} | tee -a archives/5gcn_pods_summary.txt', '\$', 6, resync=True)
+			podName = re.findall(f'{imageName}[\S\d\w]+', mySSH.getBefore())
+			isRunning = False
+			count = 0
+			while count < 2 and isRunning == False:
+				time.sleep(60)
+				mySSH.command(f'oc exec {podName} -c {nameSufix} -it -- ps aux', '\$', 6, resync=True)
+				if mySSH.getBefore().count(f'oai_{nameSufix}') != 0:
+					logging.debug(f'\u001B[1m POD "{imageName}" Service Running Sucessfully\u001B[0m')
+					isRunning = True
+					passPods += 1
+				count +=1	
+			if isRunning == False:
+				logging.error(f'\u001B[1m POD "{imageName}" Service Running FAILED \u001B[0m')
+
+		if passPods == 4:
+			logging.debug(f'\u001B[1m   Deployment: OK \u001B[0m')
+		else:
+			logging.error(f'\u001B[1m 	Deployment: KO \u001B[0m')
+			self.UnDeploy_5gcn()
+			self.AnalyzeLogFile_5gcn()
+			sys.exit(-1)
+		self.AnalyzeLogFile_5gcn()
+		
+	def UnDeploy_5gcn(self):
+		mySSH = SSH.SSHConnection()
+		mySSH.open(lIpAddr, lUserName, lPassWord)
+		mySSH.command('cd ' + lSourcePath, '\$', 5)
+		logging.debug('\u001B[1m   UnDeploying the 5gcn\u001B[0m')
+		# logging to OC Cluster and then switch to corresponding project
+		mySSH.command(f'oc login -u {ocUserName} -p {ocPassword}', '\$', 6)
+		if mySSH.getBefore().count('Login successful.') == 0:
+			logging.error('\u001B[1m OC Cluster Login Failed\u001B[0m')
+			mySSH.close()
+			sys.exit(-1)
+		else:
+			logging.debug('\u001B[1m   Login to OC Cluster Successfully\u001B[0m')
+		mySSH.command(f'oc project {ocProjectName}', '\$', 6)
+		if mySSH.getBefore().count(f'Already on project "{ocProjectName}"') == 0 and mySSH.getBefore().count(f'Now using project "{self.OCProjectName}"') == 0:
+			logging.error(f'\u001B[1m Unable to access OC project {ocProjectName}\u001B[0m')
+			mySSH.close()
+			sys.exit(-1)
+		else:
+			logging.debug(f'\u001B[1m   Now using project {ocProjectName}\u001B[0m')
+
+		# UnDeploy the 5gcn pods
+		images = self.imageTags.split(',')
+        for image in images:
+            eachImage = image.split(':')
+            imageName = eachImage(0)
+            imageTag = eachImage(1)
+			mySSH.command(f'helm uninstall {imageName} | tee -a archives/5gcn_helm_summary.txt 2>&1', '\$', 6)
+			if mySSH.getBefore().count(f'release "{imageName}" uninstalled') == 0 and mySSH.getBefore().count('release: not found') == 0:
+				logging.error(f'\u001B[1m UnDeploying "{imageName}" Failed using helm chart on OC Cluster\u001B[0m')
+			else:
+				logging.debug(f'\u001B[1m   UnDeployed "{imageName}" Successfully on OC Cluster\u001B[0m')
+			# Delete images and imagestream
+			mySSH.command(f'sudo podman rmi default-route-openshift-image-registry.apps.5glab.nsa.eurecom.fr/{self.OCProjectName}/{imageName}:{imageTag}', '\$', 6)
+			mySSH.command(f'oc delete is {imageName}', '\$', 6)
+			logging.debug(f'\u001B[1m Deleted the "{imageName}" Image and ImageStream\u001B[0m')
+		mySSH.command('oc logout', '\$', 6)
+		mySSH.close()
+		self.AnalyzeLogFile_5gcn()
+
+
+	def AnalyzeLogFile_5gcn(self):
+		pass
+
+
+
+#--------------------------------------------------------------------------------------------------------
+#
+# Start of main
+#
+#--------------------------------------------------------------------------------------------------------
+
+CN = ClusterDeploy()
+
+argvs = sys.argv
+argc = len(argvs)
+
+while len(argvs) > 1:
+    myArgv = argvs.pop(1)
+    if re.match('^\-\-mode=(.+)$', myArgv, re.IGNORECASE):
+        matchReg = re.match('^\-\-mode=(.+)$', myArgv, re.IGNORECASE)
+        CN.mode = matchReg.group(1)	
+    elif re.match('^\-\-eNBIPAddress=(.+)$', myArgv, re.IGNORECASE):
+        matchReg = re.match('^\-\-eNBIPAddress=(.+)$', myArgv, re.IGNORECASE)
+        CN.eNBIPAddress = matchReg.group(1)
+    elif re.match('^\-\-eNBUserName=(.+)$', myArgv, re.IGNORECASE):
+        matchReg = re.match('^\-\-eNBUserName=(.+)$', myArgv, re.IGNORECASE)
+        CN.eNBUserName = matchReg.group(1)
+    elif re.match('^\-\-eNBPassword=(.+)$', myArgv, re.IGNORECASE):
+        matchReg = re.match('^\-\-eNBPassword=(.+)$', myArgv, re.IGNORECASE)
+        CN.eNBPassword = matchReg.group(1)
+    elif re.match('^\-\-OCUserName=(.+)$', myArgv, re.IGNORECASE):
+        matchReg = re.match('^\-\-OCUserName=(.+)$', myArgv, re.IGNORECASE)
+        CN.OCUserName = matchReg.group(1)
+    elif re.match('^\-\-OCPassword=(.+)$', myArgv, re.IGNORECASE):
+        matchReg = re.match('^\-\-OCPassword=(.+)$', myArgv, re.IGNORECASE)
+        CN.OCPassword = matchReg.group(1)
+    elif re.match('^\-\-OCProjectName=(.+)$', myArgv, re.IGNORECASE):
+        matchReg = re.match('^\-\-OCProjectName=(.+)$', myArgv, re.IGNORECASE)
+        CN.OCProjectName = matchReg.group(1)
+    elif re.match('^\-\-imageTags=(.+)$', myArgv, re.IGNORECASE):
+        matchReg = re.match('^\-\-imageTags=(.+)$', myArgv, re.IGNORECASE)
+        CN.imageTags = matchReg.group(1)
+	else:
+		sys.exit('Invalid Parameter: ' + myArgv)
+
+if CN.mode == 'Deploy':
+	CN.Deploy_5gcn()
+elif CN.mode == 'UnDeploy':
+	CN.UnDeploy_5gcn()
\ No newline at end of file
diff --git a/ci-scripts/sshconnection.py b/ci-scripts/sshconnection.py
new file mode 100644
index 00000000..b4087c96
--- /dev/null
+++ b/ci-scripts/sshconnection.py
@@ -0,0 +1,259 @@
+#/*
+# * 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
+# */
+#---------------------------------------------------------------------
+# Python for CI of OAI-eNB + COTS-UE
+#
+#   Required Python Version
+#     Python 3.x
+#
+#   Required Python Package
+#     pexpect
+#---------------------------------------------------------------------
+
+#-----------------------------------------------------------
+# Import
+#-----------------------------------------------------------
+import pexpect          # pexpect
+import logging
+import time             # sleep
+import re
+import subprocess
+import sys
+
+#-----------------------------------------------------------
+# Class Declaration
+#-----------------------------------------------------------
+class SSHConnection():
+	def __init__(self):
+		self.ssh = ''
+		self.picocom_closure = False
+		self.ipaddress = ''
+		self.username = ''
+		self.cmd2Results = ''
+
+	def disablePicocomClosure(self):
+		self.picocom_closure = False
+
+	def enablePicocomClosure(self):
+		self.picocom_closure = True
+
+	def open(self, ipaddress, username, password):
+		count = 0
+		connect_status = False
+		while count < 4:
+			self.ssh = pexpect.spawn('ssh -o PubkeyAuthentication=no {}@{}'.format(username,ipaddress))
+			self.ssh.timeout = 5
+			self.sshresponse = self.ssh.expect(['Are you sure you want to continue connecting (yes/no)?', 'password:', 'Last login', pexpect.EOF, pexpect.TIMEOUT])
+			if self.sshresponse == 0:
+				self.ssh.sendline('yes')
+				self.sshresponse = self.ssh.expect(['password:', username + '@'])
+				if self.sshresponse == 0:
+					self.ssh.sendline(password)
+				self.sshresponse = self.ssh.expect(['\$', 'Permission denied', 'password:', pexpect.EOF, pexpect.TIMEOUT])
+				if self.sshresponse == 0:
+					count = 10
+					connect_status = True
+				else:
+					logging.debug('self.sshresponse = ' + str(self.sshresponse))
+			elif self.sshresponse == 1:
+				self.ssh.sendline(password)
+				self.sshresponse = self.ssh.expect(['\$', 'Permission denied', 'password:', pexpect.EOF, pexpect.TIMEOUT])
+				if self.sshresponse == 0:
+					count = 10
+					connect_status = True
+				else:
+					logging.debug('self.sshresponse = ' + str(self.sshresponse))
+			elif self.sshresponse == 2:
+				# Checking if we are really on the remote client defined by its IP address
+				self.command('stdbuf -o0 ifconfig | egrep --color=never "inet addr:|inet "', '\$', 5)
+				result = re.search(str(ipaddress), str(self.ssh.before))
+				if result is None:
+					self.close()
+				else:
+					count = 10
+					connect_status = True
+			else:
+				# debug output
+				logging.debug(str(self.ssh.before))
+				logging.debug('self.sshresponse = ' + str(self.sshresponse))
+			# adding a tempo when failure
+			if not connect_status:
+				time.sleep(1)
+			count += 1
+		if connect_status:
+			pass
+		else:
+			sys.exit('SSH Connection Failed')
+		self.ipaddress = ipaddress
+		self.username = username
+
+
+
+
+	def cde_check_value(self, commandline, expected, timeout):
+		logging.debug(commandline)
+		self.ssh.timeout = timeout
+		self.ssh.sendline(commandline)
+		expected.append(pexpect.EOF)
+		expected.append(pexpect.TIMEOUT)
+		self.sshresponse = self.ssh.expect(expected)
+		return self.sshresponse
+
+	def command(self, commandline, expectedline, timeout, silent=False, resync=False):
+		if not silent:
+			logging.debug(commandline)
+		self.ssh.timeout = timeout
+		# Nasty patch when pexpect output is out of sync.
+		# Much pronounced when running back-to-back-back oc commands
+		if resync:
+			self.ssh.send(commandline)
+			self.ssh.expect([commandline, pexpect.TIMEOUT])
+			self.ssh.send('\r\n')
+			self.sshresponse = self.ssh.expect([expectedline, pexpect.EOF, pexpect.TIMEOUT])
+		else:
+			self.ssh.sendline(commandline)
+			self.sshresponse = self.ssh.expect([expectedline, pexpect.EOF, pexpect.TIMEOUT])
+		if self.sshresponse == 0:
+			return 0
+		elif self.sshresponse == 1:
+			logging.debug('\u001B[1;37;41m Unexpected EOF \u001B[0m')
+			logging.debug('Expected Line : ' + expectedline)
+			logging.debug(str(self.ssh.before))
+			sys.exit(self.sshresponse)
+		elif self.sshresponse == 2:
+			logging.debug('\u001B[1;37;41m Unexpected TIMEOUT \u001B[0m')
+			logging.debug('Expected Line : ' + expectedline)
+			result = re.search('ping |iperf |picocom', str(commandline))
+			if result is None:
+				logging.debug(str(self.ssh.before))
+				sys.exit(self.sshresponse)
+			else:
+				return -1
+		else:
+			logging.debug('\u001B[1;37;41m Unexpected Others \u001B[0m')
+			logging.debug('Expected Line : ' + expectedline)
+			sys.exit(self.sshresponse)
+
+	def command2(self, commandline, timeout, silent=False):
+		if not silent:
+			logging.debug(commandline)
+		self.cmd2Results = ''
+		myHost = self.username + '@' + self.ipaddress
+		# CAUTION: THIS METHOD IMPLIES THAT THERE ARE VALID SSH KEYS
+		# BETWEEN THE PYTHON EXECUTOR NODE AND THE REMOTE HOST
+		# OTHERWISE IT WON'T WORK
+		lSsh = subprocess.Popen(["ssh", "%s" % myHost, commandline],shell=False,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
+		self.cmd2Results = str(lSsh.stdout.readlines())
+
+	def close(self):
+		self.ssh.timeout = 5
+		self.ssh.sendline('exit')
+		self.sshresponse = self.ssh.expect([pexpect.EOF, pexpect.TIMEOUT])
+		self.ipaddress = ''
+		self.username = ''
+		if self.sshresponse == 0:
+			pass
+		elif self.sshresponse == 1:
+			if not self.picocom_closure:
+				logging.debug('\u001B[1;37;41m Unexpected TIMEOUT during closing\u001B[0m')
+		else:
+			logging.debug('\u001B[1;37;41m Unexpected Others during closing\u001B[0m')
+
+	def copyin(self, ipaddress, username, password, source, destination):
+		count = 0
+		copy_status = False
+		logging.debug('scp '+ username + '@' + ipaddress + ':' + source + ' ' + destination)
+		while count < 10:
+			scp_spawn = pexpect.spawn('scp '+ username + '@' + ipaddress + ':' + source + ' ' + destination, timeout = 100)
+			scp_response = scp_spawn.expect(['Are you sure you want to continue connecting (yes/no)?', 'password:', pexpect.EOF, pexpect.TIMEOUT])
+			if scp_response == 0:
+				scp_spawn.sendline('yes')
+				scp_spawn.expect('password:')
+				scp_spawn.sendline(password)
+				scp_response = scp_spawn.expect(['\$', 'Permission denied', 'password:', pexpect.EOF, pexpect.TIMEOUT])
+				if scp_response == 0:
+					count = 10
+					copy_status = True
+				else:
+					logging.debug('1 - scp_response = ' + str(scp_response))
+			elif scp_response == 1:
+				scp_spawn.sendline(password)
+				scp_response = scp_spawn.expect(['\$', 'Permission denied', 'password:', pexpect.EOF, pexpect.TIMEOUT])
+				if scp_response == 0 or scp_response == 3:
+					count = 10
+					copy_status = True
+				else:
+					logging.debug('2 - scp_response = ' + str(scp_response))
+			elif scp_response == 2:
+				count = 10
+				copy_status = True
+			else:
+				logging.debug('3 - scp_response = ' + str(scp_response))
+			# adding a tempo when failure
+			if not copy_status:
+				time.sleep(1)
+			count += 1
+		if copy_status:
+			return 0
+		else:
+			return -1
+
+	def copyout(self, ipaddress, username, password, source, destination):
+		count = 0
+		copy_status = False
+		logging.debug('scp ' + source + ' ' + username + '@' + ipaddress + ':' + destination)
+		while count < 4:
+			scp_spawn = pexpect.spawn('scp ' + source + ' ' + username + '@' + ipaddress + ':' + destination, timeout = 100)
+			scp_response = scp_spawn.expect(['Are you sure you want to continue connecting (yes/no)?', 'password:', pexpect.EOF, pexpect.TIMEOUT])
+			if scp_response == 0:
+				scp_spawn.sendline('yes')
+				scp_spawn.expect('password:')
+				scp_spawn.sendline(password)
+				scp_response = scp_spawn.expect(['\$', 'Permission denied', 'password:', pexpect.EOF, pexpect.TIMEOUT])
+				if scp_response == 0:
+					count = 10
+					copy_status = True
+				else:
+					logging.debug('1 - scp_response = ' + str(scp_response))
+			elif scp_response == 1:
+				scp_spawn.sendline(password)
+				scp_response = scp_spawn.expect(['\$', 'Permission denied', 'password:', pexpect.EOF, pexpect.TIMEOUT])
+				if scp_response == 0 or scp_response == 3:
+					count = 10
+					copy_status = True
+				else:
+					logging.debug('2 - scp_response = ' + str(scp_response))
+			elif scp_response == 2:
+				count = 10
+				copy_status = True
+			else:
+				logging.debug('3 - scp_response = ' + str(scp_response))
+			# adding a tempo when failure
+			if not copy_status:
+				time.sleep(1)
+			count += 1
+		if copy_status:
+			pass
+		else:
+			sys.exit('SCP failed')
+
+	def getBefore(self):
+		return str(self.ssh.before)
diff --git a/openshift/oai-amf-image-stream.yml b/openshift/oai-amf-image-stream.yml
new file mode 100644
index 00000000..894517e1
--- /dev/null
+++ b/openshift/oai-amf-image-stream.yml
@@ -0,0 +1,30 @@
+#/*
+# * 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
+# */
+#---------------------------------------------------------------------
+#
+apiVersion: image.openshift.io/v1
+kind: ImageStream
+metadata:
+  name: oai-amf
+spec:
+  lookupPolicy:
+    local: true
+
diff --git a/openshift/oai-nrf-image-stream.yml b/openshift/oai-nrf-image-stream.yml
new file mode 100644
index 00000000..6587261a
--- /dev/null
+++ b/openshift/oai-nrf-image-stream.yml
@@ -0,0 +1,30 @@
+#/*
+# * 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
+# */
+#---------------------------------------------------------------------
+#
+apiVersion: image.openshift.io/v1
+kind: ImageStream
+metadata:
+  name: oai-nrf
+spec:
+  lookupPolicy:
+    local: true
+
diff --git a/openshift/oai-smf-image-stream.yml b/openshift/oai-smf-image-stream.yml
new file mode 100644
index 00000000..cd807617
--- /dev/null
+++ b/openshift/oai-smf-image-stream.yml
@@ -0,0 +1,30 @@
+#/*
+# * 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
+# */
+#---------------------------------------------------------------------
+#
+apiVersion: image.openshift.io/v1
+kind: ImageStream
+metadata:
+  name: oai-smf
+spec:
+  lookupPolicy:
+    local: true
+
diff --git a/openshift/oai-spgwu-tiny-image-stream.yml b/openshift/oai-spgwu-tiny-image-stream.yml
new file mode 100644
index 00000000..37e53693
--- /dev/null
+++ b/openshift/oai-spgwu-tiny-image-stream.yml
@@ -0,0 +1,30 @@
+#/*
+# * 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
+# */
+#---------------------------------------------------------------------
+#
+apiVersion: image.openshift.io/v1
+kind: ImageStream
+metadata:
+  name: oai-spgwu-tiny
+spec:
+  lookupPolicy:
+    local: true
+
-- 
GitLab