summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/docker/client/__init__.py1
-rw-r--r--tools/docker/client/vsperf_client.py771
-rw-r--r--tools/docker/client/vsperfclient.conf39
-rw-r--r--tools/docker/deployment/auto/controller/Dockerfile23
-rw-r--r--tools/docker/deployment/auto/controller/list.env14
-rw-r--r--tools/docker/deployment/auto/controller/vsperf/__init__.py1
-rw-r--r--tools/docker/deployment/auto/controller/vsperf/collectd.conf49
-rw-r--r--tools/docker/deployment/auto/controller/vsperf/trex_cfg.yaml20
-rw-r--r--tools/docker/deployment/auto/controller/vsperf/vsperf_controller.py392
-rw-r--r--tools/docker/deployment/auto/docker-compose.yml22
-rw-r--r--tools/docker/deployment/interactive/controller/Dockerfile21
-rw-r--r--tools/docker/deployment/interactive/controller/vsperf/__init__.py1
-rw-r--r--tools/docker/deployment/interactive/controller/vsperf/vsperf_controller.py360
-rw-r--r--tools/docker/deployment/interactive/docker-compose.yml21
-rw-r--r--tools/docker/docs/architecture.txt70
-rw-r--r--tools/docker/docs/client.rst99
-rw-r--r--tools/docker/docs/test.rst86
-rw-r--r--tools/docker/libs/proto/__init__.py1
-rwxr-xr-xtools/docker/libs/proto/vsperf.proto109
-rw-r--r--tools/docker/libs/utils/__init__.py1
-rw-r--r--tools/docker/libs/utils/exceptions.py65
-rw-r--r--tools/docker/libs/utils/ssh.py546
-rw-r--r--tools/docker/libs/utils/utils.py41
-rwxr-xr-xtools/docker/prepare.sh33
-rw-r--r--tools/docker/testcontrol/auto/controller/Dockerfile23
-rw-r--r--tools/docker/testcontrol/auto/controller/list.env13
-rw-r--r--tools/docker/testcontrol/auto/controller/vsperf/__init__.py1
-rw-r--r--tools/docker/testcontrol/auto/controller/vsperf/vsperf.conf21
-rw-r--r--tools/docker/testcontrol/auto/controller/vsperf/vsperf_controller.py469
-rw-r--r--tools/docker/testcontrol/auto/docker-compose.yml22
-rw-r--r--tools/docker/testcontrol/interactive/controller/Dockerfile22
-rw-r--r--tools/docker/testcontrol/interactive/controller/vsperf/__init__.py1
-rw-r--r--tools/docker/testcontrol/interactive/controller/vsperf/output.txt1
-rw-r--r--tools/docker/testcontrol/interactive/controller/vsperf/vsperf_controller.py706
-rw-r--r--tools/docker/testcontrol/interactive/docker-compose.yml20
35 files changed, 4085 insertions, 0 deletions
diff --git a/tools/docker/client/__init__.py b/tools/docker/client/__init__.py
new file mode 100644
index 00000000..ad0ebec3
--- /dev/null
+++ b/tools/docker/client/__init__.py
@@ -0,0 +1 @@
+#### Empty
diff --git a/tools/docker/client/vsperf_client.py b/tools/docker/client/vsperf_client.py
new file mode 100644
index 00000000..2a3e509f
--- /dev/null
+++ b/tools/docker/client/vsperf_client.py
@@ -0,0 +1,771 @@
+"""Deploy : vsperf_deploy_client"""
+#pylint: disable=import-error
+
+import configparser
+import sys
+from pathlib import Path
+
+
+import grpc
+from proto import vsperf_pb2
+from proto import vsperf_pb2_grpc
+
+CHUNK_SIZE = 1024 * 1024 # 1MB
+
+
+HEADER = r"""
+ _ _ ___ ____ ____ ____ ____ ___ __ ____ ____ _ _ ____
+( \/ )/ __)( _ \( ___)( _ \( ___) / __)( ) (_ _)( ___)( \( )(_ _)
+ \ / \__ \ )___/ )__) ) / )__) ( (__ )(__ _)(_ )__) ) ( )(
+ \/ (___/(__) (____)(_)\_)(__) \___)(____)(____)(____)(_)\_) (__)
+"""
+
+COLORS = {
+ 'blue': '\033[94m',
+ 'pink': '\033[95m',
+ 'green': '\033[92m',
+}
+
+DUT_CHECK = 0
+TGEN_CHECK = 0
+
+def colorize(string, color):
+ """Colorized HEADER"""
+ if color not in COLORS:
+ return string
+ return COLORS[color] + string + '\033[0m'
+
+
+class VsperfClient():
+ """
+ This class reprsents the VSPERF-client.
+ It talks to vsperf-docker to perform installation, configuration and
+ test-execution
+ """
+ # pylint: disable=R0904,no-else-break
+ # pylint: disable=W0603,invalid-name
+ # pylint: disable=R1710
+ def __init__(self):
+ """read vsperfclient.conf"""
+ self.cfp = 'vsperfclient.conf'
+ self.config = configparser.RawConfigParser()
+ self.config.read(self.cfp)
+ self.stub = None
+ self.dut_check = 0
+ self.tgen_check = 0
+
+ def get_mode(self):
+ """read the mode for the client"""
+ return self.config.get('Mode', 'mode')
+
+ def get_deploy_channel_info(self):
+ """get the channel data"""
+ return (self.config.get('DeployServer', 'ip'),
+ self.config.get('DeployServer', 'port'))
+
+ def get_test_channel_info(self):
+ """get the channel for tgen"""
+ return (self.config.get('TestServer', 'ip'),
+ self.config.get('TestServer', 'port'))
+
+ def create_stub(self, channel):
+ """create stub to talk to controller"""
+ self.stub = vsperf_pb2_grpc.ControllerStub(channel)
+
+ def host_connect(self):
+ """provice dut-host credential to controller"""
+ global DUT_CHECK
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ connect_reply = self.stub.HostConnect(hostinfo)
+ DUT_CHECK = 1
+ print(connect_reply.message)
+
+ def tgen_connect(self):
+ """provide tgen-host credential to controller"""
+ global TGEN_CHECK
+ tgeninfo = vsperf_pb2.HostInfo(ip=self.config.get('TGen', 'ip'),
+ uname=self.config.get('TGen', 'uname'),
+ pwd=self.config.get('TGen', 'pwd'))
+ connect_reply = self.stub.TGenHostConnect(tgeninfo)
+ TGEN_CHECK = 1
+ print(connect_reply.message)
+
+ def host_connect_both(self):
+ """provice dut-host credential to controller"""
+ global DUT_CHECK
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ connect_reply = self.stub.HostConnect(hostinfo)
+ client = VsperfClient()
+ client.automatically_test_dut_connect()
+ DUT_CHECK = 1
+ print(connect_reply.message)
+
+ def tgen_connect_both(self):
+ """provide tgen-host credential to controller"""
+ global TGEN_CHECK
+ tgeninfo = vsperf_pb2.HostInfo(ip=self.config.get('TGen', 'ip'),
+ uname=self.config.get('TGen', 'uname'),
+ pwd=self.config.get('TGen', 'pwd'))
+ connect_reply = self.stub.TGenHostConnect(tgeninfo)
+ TGEN_CHECK = 1
+ client = VsperfClient()
+ client.automatically_test_tgen_connect()
+ print(connect_reply.message)
+
+ @classmethod
+ def automatically_test_dut_connect(cls):
+ """handle automatic connection with tgen"""
+ client = VsperfClient()
+ ip_add, port = client.get_test_channel_info()
+ channel = grpc.insecure_channel(ip_add + ':' + port)
+ client.create_stub(channel)
+ client.host_testcontrol_connect()
+
+ @classmethod
+ def automatically_test_tgen_connect(cls):
+ """handle automatic connection with tgen"""
+ client = VsperfClient()
+ ip_add, port = client.get_test_channel_info()
+ channel = grpc.insecure_channel(ip_add + ':' + port)
+ client.create_stub(channel)
+ client.tgen_testcontrol_connect()
+
+ def exit_section(self):
+ """exit"""
+ @classmethod
+ def section_execute(cls, menuitems, client, ip_add, port):
+ """it will use to enter into sub-option"""
+ channel = grpc.insecure_channel(ip_add + ':' + port)
+
+ while True:
+ client.create_stub(channel)
+ while True:
+ # os.system('clear')
+ print(colorize(HEADER, 'blue'))
+ print(colorize('version 0.1\n', 'pink'))
+ for item in menuitems:
+ print(colorize("[" +
+ str(menuitems.index(item)) + "]", 'green') +
+ list(item.keys())[0])
+ choice = input(">> ")
+ try:
+ if int(choice) < 0:
+ raise ValueError
+ if (int(choice) >= 0) and (int(choice) < (len(menuitems) - 1)):
+ list(menuitems[int(choice)].values())[0]()
+ else:
+ break
+ except (ValueError, IndexError):
+ pass
+ break
+ @classmethod
+ def get_user_trex_conf_location(cls):
+ """Ask user for t-rex configuration location"""
+ while True:
+ filename_1 = str(input("Provide correct location for your t-rex configuration " \
+ "file where trex_cfg.yaml exist\n" \
+ "***************** Make Sure You Choose Correct" \
+ " File for Upload*******************\n" \
+ "Provide location: \n"))
+ user_file = Path("{}".format(filename_1.strip()))
+ if user_file.is_file():
+ break
+ else:
+ print("**************File Does Not Exist*****************\n")
+ continue
+ return filename_1
+
+ def upload_tgen_config(self):
+ """t-rex config file as a chunk to controller"""
+ if TGEN_CHECK == 0:
+ return print("TGen-Host is not Connected [!]" \
+ "\nMake sure to establish connection with TGen-Host.")
+ default_location = self.config.get('ConfFile', 'tgenpath')
+ if not default_location:
+ filename = self.get_user_trex_conf_location()
+ else:
+ user_preference = str(input("Use location specified in vsperfclient.conf?[Y/N] :"))
+ while True:
+ if 'y' in user_preference.lower().strip():
+ filename = self.config.get('ConfFile', 'tgenpath')
+ user_file = Path("{}".format(filename.strip()))
+ if user_file.is_file():
+ break
+ else:
+ print("**************File Does Not Exist*****************\n")
+ user_preference = 'n'
+ continue
+ elif 'n' in user_preference.lower().strip():
+ filename = self.get_user_trex_conf_location()
+ break
+ else:
+ print("Invalid Input")
+ user_preference = str(input("Use location specified in vsperfclient.conf?" \
+ "[Y/N] : "))
+ continue
+ filename = filename.strip()
+ chunks = self.get_file_chunks_1(filename)
+ upload_status = self.stub.TGenUploadConfigFile(chunks)
+ print(upload_status.Message)
+
+ def vsperf_install(self):
+ """vsperf install on dut-host"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ install_reply = self.stub.VsperfInstall(hostinfo)
+ print(install_reply.message)
+
+ def collectd_install(self):
+ """collectd install on dut-host"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ install_reply = self.stub.CollectdInstall(hostinfo)
+ print(install_reply.message)
+
+ def tgen_install(self):
+ """install t-rex on Tgen host"""
+ tgeninfo = vsperf_pb2.HostInfo(ip=self.config.get('TGen', 'ip'),
+ uname=self.config.get('TGen', 'uname'),
+ pwd=self.config.get('TGen', 'pwd'))
+ install_reply = self.stub.TGenInstall(tgeninfo)
+ print(install_reply.message)
+
+ @classmethod
+ def get_user_conf_location(cls):
+ """get user input for test configuration file"""
+ while True:
+ filename_1 = str(input("Provide correct location for your test configuration " \
+ "file where it exist\n" \
+ "***************** Make Sure You Choose Correct" \
+ " Test File for Upload*******************\n" \
+ "Provide location: \n"))
+ user_file = Path("{}".format(filename_1.strip()))
+ if user_file.is_file():
+ break
+ else:
+ print("**************File Does Not Exist*****************\n")
+ continue
+ return filename_1
+
+ def upload_config(self):
+ """transfer config file as a chunk to controller"""
+ if DUT_CHECK == 0:
+ return print("DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with DUT-Host.")
+ default_location = self.config.get('ConfFile', 'path')
+ if not default_location:
+ filename = self.get_user_conf_location()
+ else:
+ user_preference = str(input("Use location specified in vsperfclient.conf?[Y/N] :"))
+ while True:
+ if 'y' in user_preference.lower().strip():
+ filename = self.config.get('ConfFile', 'path')
+ user_file = Path("{}".format(filename.strip()))
+ if user_file.is_file():
+ break
+ else:
+ print("**************File Does Not Exist*****************\n")
+ user_preference = 'n'
+ continue
+ elif 'n' in user_preference.lower().strip():
+ filename = self.get_user_conf_location()
+ break
+ else:
+ print("Invalid Input")
+ user_preference = str(input("Use location specified in vsperfclient.conf?" \
+ "[Y/N] : "))
+ continue
+ filename = filename.strip()
+ upload_param = self.get_file_chunks(filename)
+ upload_status = self.stub.UploadConfigFile(upload_param)
+ print(upload_status.Message)
+
+ def start_test(self):
+ """start test parameter, test config file and test name"""
+ test_control = vsperf_pb2.ControlVsperf(testtype=self.config.get('Testcase', 'test'), \
+ conffile=self.config.get('Testcase', 'conffile'))
+ control_reply = self.stub.StartTest(test_control)
+ print(control_reply.message)
+
+ def start_tgen(self):
+ """start t-rex traffic generetor on tgen-host"""
+ tgen_control = vsperf_pb2.ControlTGen(params=self.config.get('TGen', 'params'))
+ control_reply = self.stub.StartTGen(tgen_control)
+ print(control_reply.message)
+
+ @classmethod
+ def get_file_chunks(cls, filename):
+ """convert file into chunk to stream between client and controller with filename"""
+ with open(filename, 'rb') as f_1:
+ while True:
+ file_path = filename
+ file_path_list = file_path.split("/")
+ test_filename = file_path_list[(len(file_path_list)-1)]
+ piece = f_1.read(CHUNK_SIZE)
+ if not piece:
+ return None
+ return vsperf_pb2.ConfFileTest(Content=piece, Filename=test_filename)
+ @classmethod
+ def get_file_chunks_1(cls, filename):
+ """Convert file into chunks"""
+ with open(filename, 'rb') as f:
+ while True:
+ piece = f.read(CHUNK_SIZE)
+ if len(piece) == 0:
+ return
+ yield vsperf_pb2.ConfFile(Content=piece)
+
+
+ def test_status(self):
+ """check the test_status"""
+ test_check = vsperf_pb2.StatusQuery(
+ testtype=self.config.get('Testcase', 'test'))
+ check_result_reply = self.stub.TestStatus(test_check)
+ print(check_result_reply.message)
+
+ def vsperf_terminate(self):
+ """after running test terminate vsperf on dut host"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ termination_reply = self.stub.TerminateVsperf(hostinfo)
+ print(termination_reply.message)
+
+ def start_beats(self):
+ """start beats on dut-host before running test"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ status_reply = self.stub.StartBeats(hostinfo)
+ print(status_reply.message)
+
+ def remove_vsperf(self):
+ """remove vsperf from dut-host"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ status_reply = self.stub.RemoveVsperf(hostinfo)
+ print(status_reply.message)
+
+ def remove_result_folder(self):
+ """remove resutl folder from dut-host"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ status_reply = self.stub.RemoveResultFolder(hostinfo)
+ print(status_reply.message)
+
+ def remove_config_files(self):
+ """remove all config files"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ status_reply = self.stub.RemoveUploadedConfig(hostinfo)
+ print(status_reply.message)
+
+ def remove_collectd(self):
+ """remove collectd from dut-host"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ status_reply = self.stub.RemoveCollectd(hostinfo)
+ print(status_reply.message)
+
+ def remove_everything(self):
+ """remove everything from dut host"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ status_reply = self.stub.RemoveEverything(hostinfo)
+ print(status_reply.message)
+
+ def sanity_nic_check(self):
+ """nic is available on tgen host check"""
+ tgeninfo = vsperf_pb2.HostInfo(ip=self.config.get('TGen', 'ip'),
+ uname=self.config.get('TGen', 'uname'),
+ pwd=self.config.get('TGen', 'pwd'))
+ status_reply = self.stub.SanityNICCheck(tgeninfo)
+ print(status_reply.message)
+
+ def sanity_collectd_check(self):
+ """check collecd properly running"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ status_reply = self.stub.SanityCollectdCheck(hostinfo)
+ print(status_reply.message)
+
+ def cpu_allocation_check(self):
+ """check cpu allocation"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ status_reply = self.stub.SanityCPUAllocationCheck(hostinfo)
+ print(status_reply.message)
+
+ def sanity_vnf_path(self):
+ """vnf path available on dut host"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ status_reply = self.stub.SanityVNFpath(hostinfo)
+ print(status_reply.message)
+
+ def sanity_vsperf_check(self):
+ """check vsperf correctly installed"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ status_reply = self.stub.SanityVSPERFCheck(hostinfo)
+ print(status_reply.message)
+
+ def sanity_dut_tgen_conn_check(self):
+ """check the connection between dut-host and tgen-host"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ status_reply = self.stub.SanityTgenConnDUTCheck(hostinfo)
+ print(status_reply.message)
+
+ def dut_test_availability(self):
+ """dut-host is free for test check"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ status_reply = self.stub.DUTvsperfTestAvailability(hostinfo)
+ print(status_reply.message)
+
+ def get_test_conf_from_dut(self):
+ """get the vsperf test config file from dut host for user to check"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ status_reply = self.stub.GetVSPERFConffromDUT(hostinfo)
+ print(status_reply.message)
+
+ def dut_hugepage_config(self):
+ """setup hugepages on dut-host"""
+ configparam = vsperf_pb2.HugepConf(hpmax=self.config.get('HugepageConfig', 'HpMax'), \
+ hprequested=self.config.get('HugepageConfig',\
+ 'HpRequested'))
+ config_status_reply = self.stub.DutHugepageConfig(configparam)
+ print(config_status_reply.message)
+ @classmethod
+ def get_user_collectd_conf_location(cls):
+ """get collectd configuration file location from user"""
+ while True:
+ filename_1 = str(input("Provide correct location for your collectd configuration " \
+ "file where collectd.conf exist\n" \
+ "***************** Make Sure You Choose Correct" \
+ " File for Upload*******************\n" \
+ "Provide location: \n"))
+ user_file = Path("{}".format(filename_1.strip()))
+ if user_file.is_file():
+ break
+ else:
+ print("**************File Does Not Exist*****************\n")
+ continue
+ return filename_1
+ def host_testcontrol_connect(self):
+ """provice dut-host credential to test controller"""
+ global DUT_CHECK
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ self.stub.HostConnect(hostinfo)
+
+ def tgen_testcontrol_connect(self):
+ """provide tgen-host credential to test controller"""
+ global TGEN_CHECK
+ tgeninfo = vsperf_pb2.HostInfo(ip=self.config.get('TGen', 'ip'),
+ uname=self.config.get('TGen', 'uname'),
+ pwd=self.config.get('TGen', 'pwd'))
+ self.stub.TGenHostConnect(tgeninfo)
+
+ def upload_collectd_config(self):
+ """collectd config file chunks forwarded to controller"""
+ if DUT_CHECK == 0:
+ return print("DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with DUT-Host.")
+ default_location = self.config.get('ConfFile', 'collectdpath')
+ if not default_location:
+ filename = self.get_user_collectd_conf_location()
+ else:
+ user_preference = str(input("Use location specified in vsperfclient.conf?[Y/N] :"))
+ while True:
+ if 'y' in user_preference.lower().strip():
+ filename = self.config.get('ConfFile', 'collectdpath')
+ user_file = Path("{}".format(filename.strip()))
+ if user_file.is_file():
+ break
+ else:
+ print("**************File Does Not Exist*****************\n")
+ user_preference = 'n'
+ continue
+ elif 'n' in user_preference.lower().strip():
+ filename = self.get_user_collectd_conf_location()
+ break
+ else:
+ print("Invalid Input")
+ user_preference = str(input("Use location specified in vsperfclient.conf?" \
+ "[Y/N] : "))
+ continue
+ filename = filename.strip()
+ chunks = self.get_file_chunks_1(filename)
+ upload_status = self.stub.CollectdUploadConfig(chunks)
+ print(upload_status.Message)
+
+ def dut_check_dependecies(self):
+ """check_dependecies on dut-host"""
+ hostinfo = vsperf_pb2.HostInfo(ip=self.config.get('Host', 'ip'),
+ uname=self.config.get('Host', 'uname'),
+ pwd=self.config.get('Host', 'pwd'))
+ check_reply = self.stub.CheckDependecies(hostinfo)
+ print(check_reply.message)
+
+ @classmethod
+ def establish_connection_both(cls):
+ """
+ This Function use to establish connection for vsperf to both the deploy server \
+ and testcontrol server
+ """
+ client = VsperfClient()
+ ip_add, port = client.get_deploy_channel_info()
+ print("Establish connection for vsperf")
+ menuitems_connection = [
+ {"Connect to DUT Host": client.host_connect_both},
+ {"Connect to TGen Host": client.tgen_connect_both},
+ {"Return to Previous Menu": client.exit_section}
+ ]
+ client.section_execute(menuitems_connection, client, ip_add, port)
+ @classmethod
+ def establish_connection_deploy(cls):
+ """
+ This Function use to establish connection for vsperf to either using the dploy
+ or using the testcontrol server
+ """
+ client = VsperfClient()
+ ip_add, port = client.get_deploy_channel_info()
+ print("Establish connection for vsperf")
+ menuitems_connection = [
+ {"Connect to DUT Host": client.host_connect},
+ {"Connect to TGen Host": client.tgen_connect},
+ {"Return to Previous Menu": client.exit_section}
+ ]
+ client.section_execute(menuitems_connection, client, ip_add, port)
+ @classmethod
+ def establish_connection_test(cls):
+ """
+ This Function use to establish connection for vsperf to either using the dploy
+ or using the testcontrol server
+ """
+ client = VsperfClient()
+ ip_add, port = client.get_test_channel_info()
+ print("Establish connection for vsperf")
+ menuitems_connection = [
+ {"Connect to DUT Host": client.host_connect},
+ {"Connect to TGen Host": client.tgen_connect},
+ {"Return to Previous Menu": client.exit_section}
+ ]
+ client.section_execute(menuitems_connection, client, ip_add, port)
+ @classmethod
+ def vsperf_setup(cls):
+ """setup sub-options"""
+ client = VsperfClient()
+ ip_add, port = client.get_deploy_channel_info()
+ print("Prerequisites Installation for VSPERF")
+ menuitems_setup = [
+ {"Install VSPERF": client.vsperf_install},
+ {"Install TGen ": client.tgen_install},
+ {"Install Collectd": client.collectd_install},
+ {"Return to Previous Menu": client.exit_section}
+ ]
+ client.section_execute(menuitems_setup, client, ip_add, port)
+ @classmethod
+ def upload_config_files(cls):
+ """all the upload sub-options"""
+ client = VsperfClient()
+ ip_add, port = client.get_deploy_channel_info()
+ menuitems_setup = [
+ {"Upload TGen Configuration File": client.upload_tgen_config},
+ {"Upload Collectd Configuration File": client.upload_collectd_config},
+ {"Return to Previous Menu": client.exit_section}
+ ]
+ client.section_execute(menuitems_setup, client, ip_add, port)
+ @classmethod
+ def manage_sysparam_config(cls):
+ """manage system parameter on dut host before run test"""
+ client = VsperfClient()
+ ip_add, port = client.get_deploy_channel_info()
+ menuitems_setup = [
+ {"DUT-Host hugepages configuration": client.dut_hugepage_config},
+ {"Check VSPERF Dependencies on DUT-Host": client.dut_check_dependecies},
+ {"Return to Previous Menu": client.exit_section}
+ ]
+ client.section_execute(menuitems_setup, client, ip_add, port)
+
+ @classmethod
+ def test_status_check(cls):
+ """after running test , test status related sub-options"""
+ client = VsperfClient()
+ ip_add, port = client.get_test_channel_info()
+ menuitems_setup = [
+ {"Test status": client.test_status},
+ {"Get Test Configuration file from DUT-host": client.get_test_conf_from_dut},
+ {"Return to Previous Menu": client.exit_section}
+ ]
+ client.section_execute(menuitems_setup, client, ip_add, port)
+
+ @classmethod
+ def sanity_check_options(cls):
+ """all sanity check sub-options"""
+ client = VsperfClient()
+ ip_add, port = client.get_test_channel_info()
+ menuitems_setup = [
+ {"Check installed VSPERF": client.sanity_vsperf_check},
+ {"Check Test Config's VNF path is available on DUT-Host": client.sanity_vnf_path},
+ {"Check NIC PCIs is available on Traffic Generator": client.sanity_nic_check},
+ {"Check CPU allocation on DUT-Host": client.cpu_allocation_check},
+ {"Check installed Collectd": client.sanity_collectd_check},
+ {"Check Connection between DUT-Host and Traffic Generator Host":
+ client.sanity_dut_tgen_conn_check},
+ {"Return to Previous Menu": client.exit_section}
+ ]
+ client.section_execute(menuitems_setup, client, ip_add, port)
+
+ @classmethod
+ def run_test(cls):
+ """run test sub-options"""
+ print("**Before user Run Tests we highly recommend user to perform Sanity Checks.......")
+ client = VsperfClient()
+ ip_add, port = client.get_test_channel_info()
+ menuitems_setup = [
+ {"Upload Test Configuration File": client.upload_config},
+ {"Perform Sanity Checks before running tests": client.sanity_check_options},
+ {"Check if DUT-HOST is available": client.dut_test_availability},
+ {"Start TGen ": client.start_tgen},
+ {"Start Beats": client.start_beats},
+ {"Start Test": client.start_test},
+ {"Return to Previous Menu": client.exit_section}
+ ]
+ client.section_execute(menuitems_setup, client, ip_add, port)
+
+ @classmethod
+ def clean_up(cls):
+ """clean-up sub-options"""
+ print(
+ "*******************************************************************\n\n\
+ IF you are performing Test on IntelPOD 12 - Node 4, Be careful during removal\n\n\
+ *******************************************************************")
+ client = VsperfClient()
+ ip_add, port = client.get_test_channel_info()
+ menuitems_setup = [
+ {"Remove VSPERF": client.remove_vsperf},
+ {"Terminate VSPERF": client.vsperf_terminate},
+ {"Remove Results from DUT-Host": client.remove_result_folder},
+ {"Remove Uploaded Configuration File": client.remove_config_files},
+ {"Remove Collectd": client.remove_collectd},
+ {"Remove Everything": client.remove_everything},
+ {"Return to Previous Menu": client.exit_section}
+
+ ]
+ client.section_execute(menuitems_setup, client, ip_add, port)
+
+def run():
+ """It will run the actul primary options"""
+ client = VsperfClient()
+ client_mode = client.get_mode()
+ print(client_mode)
+ if "deploy" in client_mode.lower().strip():
+ menuitems = [
+ {"Establish Connections": client.establish_connection_deploy},
+ {"Installation": client.vsperf_setup},
+ {"Upload Configuration Files": client.upload_config_files},
+ {"Manage DUT-System Configuration": client.manage_sysparam_config},
+ {"Exit": sys.exit}
+ ]
+ #ip_add, port = client.get_channel_info()
+ #channel = grpc.insecure_channel(ip_add + ':' + port)
+ while True:
+ # os.system('clear')
+ print(colorize(HEADER, 'blue'))
+ print(colorize('version 0.1\n', 'pink'))
+ for item in menuitems:
+ print(colorize("[" +
+ str(menuitems.index(item)) + "]", 'green') +
+ list(item.keys())[0])
+ choice = input(">> ")
+ try:
+ if int(choice) < 0:
+ raise ValueError
+ list(menuitems[int(choice)].values())[0]()
+ except (ValueError, IndexError):
+ pass
+
+ elif "test" in client_mode.lower().strip():
+ menuitems = [
+ {"Establish Connections": client.establish_connection_test},
+ {"Run Test": client.run_test},
+ {"Test Status": client.test_status_check},
+ {"Clean-Up": client.clean_up},
+ {"Exit": sys.exit}
+ ]
+ #ip_add, port = client.get_channel_info()
+ #channel = grpc.insecure_channel(ip_add + ':' + port)
+ while True:
+ # os.system('clear')
+ print(colorize(HEADER, 'blue'))
+ print(colorize('version 0.1\n', 'pink'))
+ for item in menuitems:
+ print(colorize("[" +
+ str(menuitems.index(item)) + "]", 'green') +
+ list(item.keys())[0])
+ choice = input(">> ")
+ try:
+ if int(choice) < 0:
+ raise ValueError
+ list(menuitems[int(choice)].values())[0]()
+ except (ValueError, IndexError):
+ pass
+
+ elif "together" in client_mode.lower().strip():
+ menuitems = [
+ {"Establish Connections": client.establish_connection_both},
+ {"Installation": client.vsperf_setup},
+ {"Upload Configuration Files": client.upload_config_files},
+ {"Manage DUT-System Configuration": client.manage_sysparam_config},
+ {"Run Test": client.run_test},
+ {"Test Status": client.test_status_check},
+ {"Clean-Up": client.clean_up},
+ {"Exit": sys.exit}
+ ]
+ #ip_add, port = client.get_channel_info()
+ #channel = grpc.insecure_channel(ip_add + ':' + port)
+ while True:
+ # os.system('clear')
+ print(colorize(HEADER, 'blue'))
+ print(colorize('version 0.1\n', 'pink'))
+ for item in menuitems:
+ print(colorize("[" +
+ str(menuitems.index(item)) + "]", 'green') +
+ list(item.keys())[0])
+ choice = input(">> ")
+ try:
+ if int(choice) < 0:
+ raise ValueError
+ list(menuitems[int(choice)].values())[0]()
+ except (ValueError, IndexError):
+ pass
+
+ else:
+ print("You have not defined client mode in vsperfclient.conf [!]")
+
+
+if __name__ == '__main__':
+ run()
diff --git a/tools/docker/client/vsperfclient.conf b/tools/docker/client/vsperfclient.conf
new file mode 100644
index 00000000..12a657d7
--- /dev/null
+++ b/tools/docker/client/vsperfclient.conf
@@ -0,0 +1,39 @@
+[DeployServer]
+ip = 127.0.0.1
+port = 50051
+
+[TestServer]
+ip = 127.0.0.1
+port = 50052
+
+[Mode]
+#Deploy: To perform only for the vsperf-setup purpose
+#Test: To perform only test
+#Together: To perform as well as test.
+#assign any value from the above option according to your requirement
+mode = Together
+
+[Host]
+ip = 10.10.120.24
+uname = opnfv
+pwd = opnfv
+
+[TGen]
+ip = 10.10.120.25
+uname = root
+pwd = P@ssw0rd
+params = -i --no-scapy-server --nc --no-watchdog
+
+[HugepageConfig]
+HpMax = 8192
+HpRequested = 1024
+
+#provide appropriate location for configuration files
+[ConfFile]
+path =
+tgenpath =
+collectdpath =
+
+[Testcase]
+test = phy2phy_tput
+conffile = vsperf.conf
diff --git a/tools/docker/deployment/auto/controller/Dockerfile b/tools/docker/deployment/auto/controller/Dockerfile
new file mode 100644
index 00000000..e849d8f2
--- /dev/null
+++ b/tools/docker/deployment/auto/controller/Dockerfile
@@ -0,0 +1,23 @@
+FROM python:3.6
+LABEL maintainer="sridhar.rao@spirent.com"
+
+ENV GRPC_PYTHON_VERSION 1.4.0
+RUN apt-get update && apt-get -y install python3-pip && apt-get -y install openssh-server
+RUN pip3 install grpcio==${GRPC_PYTHON_VERSION} grpcio-tools==${GRPC_PYTHON_VERSION}
+RUN pip3 install paramiko
+RUN pip3 install chainmap
+RUN pip3 install oslo.utils
+RUN pip3 install scp
+
+WORKDIR /usr/src/app
+
+COPY ./vsperf ./vsperf
+
+VOLUME ["/usr/src/app/vsperf"]
+
+EXPOSE 50051
+
+CMD ["python3", "./vsperf/vsperf_controller.py"]
+
+#CMD tail -f /dev/null
+
diff --git a/tools/docker/deployment/auto/controller/list.env b/tools/docker/deployment/auto/controller/list.env
new file mode 100644
index 00000000..ab4404b7
--- /dev/null
+++ b/tools/docker/deployment/auto/controller/list.env
@@ -0,0 +1,14 @@
+DUT_IP_ADDRESS=10.10.120.24
+DUT_USERNAME=opnfv
+DUT_PASSWORD=opnfv
+
+TGEN_IP_ADDRESS=10.10.120.25
+TGEN_USERNAME=root
+TGEN_PASSWORD=P@ssw0rd
+TGEN_PARAMS= -i --no-scapy-server --nc --no-watchdog
+
+HUGEPAGE_MAX=8192
+HUGEPAGE_REQUESTED=1024
+
+SANITY_CHECK=NO
+
diff --git a/tools/docker/deployment/auto/controller/vsperf/__init__.py b/tools/docker/deployment/auto/controller/vsperf/__init__.py
new file mode 100644
index 00000000..ad0ebec3
--- /dev/null
+++ b/tools/docker/deployment/auto/controller/vsperf/__init__.py
@@ -0,0 +1 @@
+#### Empty
diff --git a/tools/docker/deployment/auto/controller/vsperf/collectd.conf b/tools/docker/deployment/auto/controller/vsperf/collectd.conf
new file mode 100644
index 00000000..9cefc8c5
--- /dev/null
+++ b/tools/docker/deployment/auto/controller/vsperf/collectd.conf
@@ -0,0 +1,49 @@
+Hostname "pod12-node4"
+Interval 1
+LoadPlugin intel_rdt
+LoadPlugin processes
+LoadPlugin interface
+LoadPlugin network
+LoadPlugin ovs_stats
+LoadPlugin cpu
+LoadPlugin memory
+LoadPlugin csv
+#LoadPlugin dpdkstat
+##############################################################################
+# Plugin configuration #
+##############################################################################
+<Plugin processes>
+ ProcessMatch "ovs-vswitchd" "ovs-vswitchd"
+ ProcessMatch "ovsdb-server" "ovsdb-server"
+ ProcessMatch "collectd" "collectd"
+</Plugin>
+<Plugin network>
+ Server "10.10.120.22" "25826"
+</Plugin>
+
+<Plugin ovs_stats>
+ Port "6640"
+ Address "127.0.0.1"
+ Socket "/usr/local/var/run/openvswitch/db.sock"
+ Bridges "vsperf-br0"
+</Plugin>
+
+<Plugin "intel_rdt">
+ Cores "2" "4-5" "6-7" "8" "9" "22" "23" "24" "25" "26" "27"
+</Plugin>
+
+<Plugin csv>
+ DataDir "/tmp/csv"
+ StoreRates false
+</Plugin>
+
+#<Plugin dpdkstat>
+# <EAL>
+# Coremask "0x1"
+# MemoryChannels "4"
+# FilePrefix "rte"
+# </EAL>
+# SharedMemObj "dpdk_collectd_stats_0"
+# EnabledPortMask 0xffff
+#</Plugin>
+
diff --git a/tools/docker/deployment/auto/controller/vsperf/trex_cfg.yaml b/tools/docker/deployment/auto/controller/vsperf/trex_cfg.yaml
new file mode 100644
index 00000000..8bb8e341
--- /dev/null
+++ b/tools/docker/deployment/auto/controller/vsperf/trex_cfg.yaml
@@ -0,0 +1,20 @@
+- port_limit : 2
+ version : 2
+ interfaces : ["81:00.0", "81:00.1"] # list of the interfaces to bind # node 4
+ port_bandwidth_gb : 10 #10G nics
+ port_info : # set eth mac addr
+ - dest_mac : "3c:fd:fe:b4:41:09" # port 0
+ src_mac : "3c:fd:fe:b4:41:08"
+ - dest_mac : "3c:fd:fe:b4:41:08" # port 1
+ src_mac : "3c:fd:fe:b4:41:09"
+ platform :
+ master_thread_id : 17
+ latency_thread_id : 16
+ dual_if :
+ - socket : 1
+ threads : [22,23,24,25,26,27]
+ - socket : 0
+ threads : [10,11,12,13,14,15]
+
+
+
diff --git a/tools/docker/deployment/auto/controller/vsperf/vsperf_controller.py b/tools/docker/deployment/auto/controller/vsperf/vsperf_controller.py
new file mode 100644
index 00000000..b6865272
--- /dev/null
+++ b/tools/docker/deployment/auto/controller/vsperf/vsperf_controller.py
@@ -0,0 +1,392 @@
+# Copyright 2018-19 Spirent Communications.
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+"""
+VSPERF_deploy_auto
+"""
+# pylint: disable=W0603
+
+import os
+import sys
+from utils import ssh
+
+_ONE_DAY_IN_SECONDS = 60 * 60 * 24
+
+DUT_IP = os.getenv('DUT_IP_ADDRESS')
+DUT_USER = os.getenv('DUT_USERNAME')
+DUT_PWD = os.getenv('DUT_PASSWORD')
+
+TGEN_IP = os.getenv('TGEN_IP_ADDRESS')
+TGEN_USER = os.getenv('TGEN_USERNAME')
+TGEN_PWD = os.getenv('TGEN_PASSWORD')
+TGEN_PARAM = os.getenv('TGEN_PARAMS')
+
+HPMAX = int(os.getenv('HUGEPAGE_MAX'))
+HPREQUESTED = int(os.getenv('HUGEPAGE_REQUESTED'))
+
+SANITY = str(os.getenv('SANITY_CHECK'))#
+
+DUT_CLIENT = None
+TGEN_CLIENT = None
+
+
+def host_connect():
+ """
+ Handle host connectivity to DUT
+ """
+ global DUT_CLIENT
+ DUT_CLIENT = ssh.SSH(host=DUT_IP, user=DUT_USER, password=DUT_PWD)
+ print("DUT-Host Successfully Connected .........................................[OK] \n ")
+
+def tgen_connect():
+ """
+ Handle Tgen Connection to Trex
+ """
+ global TGEN_CLIENT
+ TGEN_CLIENT = ssh.SSH(host=TGEN_IP, user=TGEN_USER, password=TGEN_PWD)
+ print("Traffic Generator Successfully Connected ...............................[OK] \n ")
+
+
+def vsperf_install():
+ """
+ Perform actual installation
+ """
+ vsperf_check_command = "source ~/vsperfenv/bin/activate ; "
+ vsperf_check_command += "cd vswitchperf && ./vsperf --help"
+ vsperf_check_cmd_result = str(DUT_CLIENT.execute(vsperf_check_command)[1])
+ vsperf_verify_list = [
+ 'usage',
+ 'positional arguments',
+ 'optional arguments',
+ 'test selection options',
+ 'test behavior options']
+ for idx, i in enumerate(vsperf_verify_list, start=1):
+ if str(i) in vsperf_check_cmd_result:
+ if idx < 5:
+ continue
+ elif idx == 5:
+ print(
+ "VSPERF is Already Installed on DUT-Host..........................."\
+ ".......[OK]\n")
+ else:
+ download_cmd = "git clone https://gerrit.opnfv.org/gerrit/vswitchperf"
+ DUT_CLIENT.run(download_cmd)
+ install_cmd = "cd vswitchperf/systems ; "
+ install_cmd += "echo '{}' | sudo -S ./build_base_machine.sh ".\
+ format(DUT_PWD)
+ DUT_CLIENT.run(install_cmd)
+ print(
+ "Vsperf Installed on DUT-Host ....................................[OK]\n")
+
+
+def tgen_install():
+ """
+ Install T-rex traffic gen on TGen
+ """
+ kill_cmd = "pkill -f ./t-rex"
+ TGEN_CLIENT.send_command(kill_cmd)
+ tgen_start_check = "cd trex/scripts && ./t-rex-64 -f cap2/dns.yaml -d 100 -m 1 --nc"
+ tgen_start_cmd_result = int(TGEN_CLIENT.execute(tgen_start_check)[0])
+ if tgen_start_cmd_result == 0:
+ print(
+ "The Host has T-rex Installed....................................[OK]\n")
+ else:
+ download_cmd = "git clone https://github.com/cisco-system-traffic-generator/trex-core trex"
+ TGEN_CLIENT.run(download_cmd)
+ install_cmd = "cd trex-core/linux_dpdk ; ./b configure ; ./b build"
+ TGEN_CLIENT.run(install_cmd)
+ print(
+ "The Host has now T-rex Installed...........................[OK]\n")
+
+def upload_tgen_config_file():
+ """
+ Upload Tgen Config File on T-rex
+ """
+ localpath = '/usr/src/app/vsperf/trex_cfg.yaml'
+ if not os.path.exists(localpath):
+ print("TGEN config File does not exist................[Failed]")
+ return
+ remotepath = '~/trex_cfg.yaml'
+ check_trex_config_cmd = "echo {} | sudo -S find /etc -maxdepth 1 -name '{}'".format(
+ TGEN_PWD, remotepath[2:])
+ check_test_result = str(TGEN_CLIENT.execute(check_trex_config_cmd)[1])
+ if remotepath[2:] in check_test_result:
+ DUT_CLIENT.run("rm -f /etc/{}".format(remotepath[2:]))
+ TGEN_CLIENT.put_file(localpath, remotepath)
+ TGEN_CLIENT.run(
+ "echo {} | sudo -S mv ~/{} /etc/".format(TGEN_PWD, remotepath[2:]), pty=True)
+ print(
+ "T-rex Configuration File Uploaded on TGen-Host...........................[OK]\n")
+
+
+def install_collectd():
+ """
+ installation of the collectd
+ """
+ check_collectd_config_cmd = "find /opt -maxdepth 1 -name 'collectd'"
+ check_test_result = str(DUT_CLIENT.execute(check_collectd_config_cmd)[1])
+ if "collectd" in check_test_result:
+ print(
+ 'Collectd Installed Successfully on DUT-Host..............................[OK]\n')
+ else:
+ download_cmd = "git clone https://github.com/collectd/collectd.git"
+ DUT_CLIENT.run(download_cmd)
+ build_cmd = "cd collectd ; "
+ build_cmd += "./build.sh"
+ DUT_CLIENT.run(build_cmd)
+ config_cmd = "cd collectd ; ./configure --enable-syslog --enable-logfile "
+ config_cmd += "--enable-hugepages --enable-debug ; "
+ DUT_CLIENT.run(config_cmd)
+ install_cmd = "cd collectd ; make ; "
+ install_cmd += "echo '{}' | sudo -S make install".format(DUT_PWD)
+ DUT_CLIENT.run(install_cmd, pty=True)
+ print(
+ 'Collectd Installed Successfully on DUT-Host.............................[OK]\n ')
+
+
+def collectd_upload_config():
+ """
+ Upload Configuration file of Collectd on DUT
+ """
+ localpath = '/usr/src/app/vsperf/collectd.conf'
+ if not os.path.exists(localpath):
+ print("Collectd config File does not exist.......................[Failed]")
+ return
+ remotepath = '~/collectd.conf'
+ collectd_config_cmd = "echo {} | sudo -S find /opt/collectd/etc -maxdepth 1 -name '{}'".\
+ format(DUT_PWD, remotepath[2:])
+ check_test_result = str(DUT_CLIENT.execute(collectd_config_cmd)[1])
+ if remotepath[2:] in check_test_result:
+ DUT_CLIENT.run(
+ "echo {} | sudo -S rm -f /opt/collectd/etc/{}".format(DUT_PWD, remotepath[2:]))
+ DUT_CLIENT.put_file(localpath, remotepath)
+ DUT_CLIENT.run("echo {} | sudo -S mv ~/{} /opt/collectd/etc/".\
+ format(DUT_PWD, remotepath[2:]), pty=True)
+ print(
+ "Collectd Configuration File Uploaded on DUT-Host.........................[OK]\n ")
+
+def start_tgen():
+ """
+ It will start the Traffic generetor
+ """
+ kill_cmd = "pkill -f ./t-rex"
+ TGEN_CLIENT.send_command(kill_cmd)
+ run_cmd = "cd trex_2.37/scripts && "
+ run_cmd += "screen ./t-rex-64 "
+ run_cmd += TGEN_PARAM
+ TGEN_CLIENT.send_command(run_cmd)
+ print(
+ "T-Rex Successfully running...............................................[OK]\n")
+
+
+def dut_hugepage_config():
+ """
+ Configure the DUT system hugepage parameter from client
+ """
+ if not HPMAX or not HPREQUESTED:
+ print("HPMAX and HPREQUESTED not defined ...................[Failed]")
+ return
+ hugepage_cmd = "echo '{}' | sudo -S mkdir -p /mnt/huge ; ".format(
+ DUT_PWD)
+ hugepage_cmd += "echo '{}' | sudo -S mount -t hugetlbfs nodev /mnt/huge".format(
+ DUT_PWD)
+ DUT_CLIENT.run(hugepage_cmd, pty=True)
+ hp_nr_cmd = "cat /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages"
+ hp_free_cmd = "cat /sys/devices/system/node/node0/hugepages/hugepages-2048kB/free_hugepages"
+ hp_nr = int(DUT_CLIENT.execute(hp_nr_cmd)[1])
+ hp_free = int(DUT_CLIENT.execute(hp_free_cmd)[1])
+ if hp_free <= HPREQUESTED:
+ hp_nr_new = hp_nr + (HPREQUESTED - hp_free)
+ if hp_nr_new > HPMAX:
+ hp_nr_new = HPMAX
+
+ nr_hugepage_cmd = "echo '{}' | sudo -S bash -c \"echo 'vm.nr_hugepages={}' >> ".\
+ format(DUT_PWD, hp_nr_new)
+ nr_hugepage_cmd += "/etc/sysctl.conf\""
+ DUT_CLIENT.run(nr_hugepage_cmd, pty=True)
+
+ dict_cmd = "cat /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages"
+ dict_check = int(DUT_CLIENT.execute(dict_cmd)[0])
+ if dict_check == 0:
+ node1_hugepage_cmd = "echo '{}' | sudo -s bash -c \"echo 0 > ".format(DUT_PWD)
+ node1_hugepage_cmd += "/sys/devices/system/node/node1/hugepages"
+ node1_hugepage_cmd += "/hugepages-2048kB/nr_hugepages\""
+ DUT_CLIENT.run(node1_hugepage_cmd, pty=True)
+ print("DUT-Host system configured with {} No of Hugepages.....................[OK] \n ".\
+ format(hp_nr_new))
+
+
+def sanity_nic_check():
+ """
+ Check either NIC PCI ids are Correctly placed or not
+ """
+ trex_conf_path = "cat /etc/trex_cfg.yaml | grep interfaces"
+ trex_conf_read = TGEN_CLIENT.execute(trex_conf_path)[1]
+ nic_pid_ids_list = [trex_conf_read.split("\"")[1], trex_conf_read.split("\"")[3]]
+ trex_nic_pic_id_cmd = "lspci | egrep -i --color 'network|ethernet'"
+ trex_nic_pic_id = str(TGEN_CLIENT.execute(trex_nic_pic_id_cmd)[1]).split('\n')
+ acheck = 0
+ for k in trex_nic_pic_id:
+ for j in nic_pid_ids_list:
+ if j in k:
+ acheck += 1
+ else:
+ pass
+ if acheck == 2:
+ print("Both the NIC PCI Ids are Correctly"\
+ " configured on TGen-Host...............[OK]\n")
+ else:
+ print("You configured NIC PCI Ids Wrong in "\
+ "TGen-Host............................[OK]\n")
+
+
+def sanity_collectd_check():
+ """
+ Check and verify collectd is able to run and start properly
+ """
+ check_collectd_cmd = "find /opt -maxdepth 1 -name 'collectd'"
+ check_test_result = str(DUT_CLIENT.execute(check_collectd_cmd)[1])
+ if "collectd" in check_test_result:
+ check_collectd_run_cmd = "echo {} | sudo -S service collectd start".format(
+ DUT_PWD)
+ DUT_CLIENT.run(check_collectd_run_cmd, pty=True)
+ check_collectd_status_cmd = "ps aux | grep collectd"
+ check_collectd_status = str(
+ DUT_CLIENT.execute(check_collectd_status_cmd)[1])
+ if "/sbin/collectd" in check_collectd_status:
+ print(
+ "Collectd is working Fine ................................................[OK] \n ")
+ else:
+ print(
+ "Collectd Fail to Start, Install correctly before running Test....[Failed]\n ")
+ else:
+ print(
+ "Collectd is not installed yet........................................[Failed]\n")
+
+def sanity_vsperf_check():
+ """
+ We have to make sure that VSPERF install correctly
+ """
+ if not DUT_CLIENT:
+ print("The Client is disconnected................................[Failed]")
+ return
+ vsperf_check_cmd = "source ~/vsperfenv/bin/activate ; cd vswitchperf && ./vsperf --help"
+ vsperf_check_cmd_result = str(DUT_CLIENT.execute(vsperf_check_cmd)[1])
+ vsperf_verify_list = [
+ 'usage',
+ 'positional arguments',
+ 'optional arguments',
+ 'test selection options',
+ 'test behavior options']
+ for idx, i in enumerate(vsperf_verify_list, start=1):
+ if str(i) in vsperf_check_cmd_result:
+ if idx < 5:
+ continue
+ elif idx == 5:
+ print(
+ "VSPERF Installed Correctly and Working fine.........................."\
+ "....[OK]\n")
+ else:
+ print(
+ "VSPERF Does Not Installed Correctly , INSTALL IT AGAIN........[Critical]\n")
+ else:
+ print(
+ "VSPERF Does Not Installed Correctly , INSTALL IT AGAIN............[Critical]\n")
+ break
+
+
+def sanity_tgen_conn_dut_check():
+ """
+ We should confirm the DUT connectivity with the Tgen and Traffic Generator is working or not
+ """
+ if not DUT_CLIENT or not TGEN_CLIENT:
+ print("The Client is disconnected................................[Failed]")
+ return
+ tgen_connectivity_check_cmd = "ping {} -c 1".format(TGEN_IP)
+ tgen_connectivity_check_result = int(
+ DUT_CLIENT.execute(tgen_connectivity_check_cmd)[0])
+ if tgen_connectivity_check_result == 0:
+ print(
+ "DUT-Host is successfully reachable to Traffic Generator Host.............[OK]\n")
+ else:
+ print(
+ "DUT-host is unsuccessful to reach the Traffic Generator Host..............[Failed]")
+ print(
+ "Make sure to establish connection before running Test...............[Critical]\n")
+
+
+def sanity_tgen_check():
+ """
+ It will check Trex properly running or not
+ """
+ if not TGEN_CLIENT:
+ print("The Client is disconnected................................[Failed]")
+ return
+ tgen_start_cmd_check = "cd trex/scripts &&"
+ tgen_start_cmd_check += " ./t-rex-64 -f cap2/dns.yaml -d 100 -m 1 --nc"
+ tgen_start_cmd_result = int(TGEN_CLIENT.execute(tgen_start_cmd_check)[0])
+ if tgen_start_cmd_result == 0:
+ print(
+ "TGen-Host successfully running........................................[OK]\n")
+ else:
+ print("TGen-Host is unable to start t-rex ..................[Failed]")
+ print("Make sure you install t-rex correctly ...............[Critical]\n")
+
+
+def dut_vsperf_test_availability():
+ """
+ Before running test we have to make sure there is no other test running
+ """
+ vsperf_ava_cmd = "ps -ef | grep -v grep | grep ./vsperf | awk '{print $2}'"
+ vsperf_ava_result = len(
+ (DUT_CLIENT.execute(vsperf_ava_cmd)[1]).split("\n"))
+ if vsperf_ava_result == 1:
+ print("DUT-Host is available for performing VSPERF Test\n\
+ You can perform Test!")
+ else:
+ print("DUT-Host is busy right now, Wait for some time\n\
+ Always Check availability before Running Test!\n")
+
+if DUT_IP:
+ host_connect()
+if not DUT_CLIENT:
+ print('Failed to connect to DUT ...............[Critical]')
+ sys.exit()
+else:
+ vsperf_install()
+ install_collectd()
+ collectd_upload_config()
+ dut_hugepage_config()
+ dut_vsperf_test_availability()
+if TGEN_IP:
+ tgen_connect()
+if not TGEN_CLIENT:
+ print('Failed to connect to TGEN_HOST.............[Critical]')
+ sys.exit()
+else:
+ tgen_install()
+ upload_tgen_config_file()
+ sanity_nic_check()
+ start_tgen()
+
+print("\n\nIF you are getting any Failed or Critical message!!!\n" \
+ "Please follow this steps:\n"
+ "1. Make necessory changes before running VSPERF TEST\n"\
+ "2. Re-Run the auto deployment container")
+
+if SANITY and 'yes' in SANITY.lower():
+ sanity_collectd_check()
+ sanity_vsperf_check()
+ sanity_tgen_check()
+ sanity_tgen_conn_dut_check()
diff --git a/tools/docker/deployment/auto/docker-compose.yml b/tools/docker/deployment/auto/docker-compose.yml
new file mode 100644
index 00000000..b5b808d2
--- /dev/null
+++ b/tools/docker/deployment/auto/docker-compose.yml
@@ -0,0 +1,22 @@
+version: '2'
+
+services:
+ deploy:
+ build:
+ context: ./controller
+ volumes:
+ - ./controller/vsperf:/vsperf
+ env_file:
+ - ./controller/list.env
+ ports:
+ - 50051
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/docker/deployment/interactive/controller/Dockerfile b/tools/docker/deployment/interactive/controller/Dockerfile
new file mode 100644
index 00000000..3d9fca42
--- /dev/null
+++ b/tools/docker/deployment/interactive/controller/Dockerfile
@@ -0,0 +1,21 @@
+FROM python:3.6
+LABEL maintainer="sridhar.rao@spirent.com"
+
+ENV GRPC_PYTHON_VERSION 1.4.0
+RUN apt-get update && apt-get -y install python3-pip
+RUN pip3 install grpcio==${GRPC_PYTHON_VERSION} grpcio-tools==${GRPC_PYTHON_VERSION}
+RUN pip3 install paramiko
+RUN pip3 install chainmap
+RUN pip3 install oslo.utils
+RUN pip3 install scp
+
+WORKDIR /usr/src/app
+
+COPY ./vsperf ./vsperf
+
+VOLUME ["/usr/src/app/vsperf"]
+
+CMD ["python3", "./vsperf/vsperf_controller.py"]
+
+
+
diff --git a/tools/docker/deployment/interactive/controller/vsperf/__init__.py b/tools/docker/deployment/interactive/controller/vsperf/__init__.py
new file mode 100644
index 00000000..ad0ebec3
--- /dev/null
+++ b/tools/docker/deployment/interactive/controller/vsperf/__init__.py
@@ -0,0 +1 @@
+#### Empty
diff --git a/tools/docker/deployment/interactive/controller/vsperf/vsperf_controller.py b/tools/docker/deployment/interactive/controller/vsperf/vsperf_controller.py
new file mode 100644
index 00000000..b192c493
--- /dev/null
+++ b/tools/docker/deployment/interactive/controller/vsperf/vsperf_controller.py
@@ -0,0 +1,360 @@
+# Copyright 2018-19 Spirent Communications.
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+# pylint: disable=R0902
+# Sixteen is reasonable instance attributes
+# pylint: disable=W0221
+"""
+VSPER docker-controller.
+"""
+
+import io
+import time
+from concurrent import futures
+import grpc
+
+from proto import vsperf_pb2
+from proto import vsperf_pb2_grpc
+from utils import ssh
+
+_ONE_DAY_IN_SECONDS = 60 * 60 * 24
+
+
+# pylint: disable=too-few-public-methods,no-self-use
+class PseudoFile(io.RawIOBase):
+ """
+ Handle ssh command output.
+ """
+
+ def write(self, chunk):
+ """
+ Write to file
+ """
+ if "error" in chunk:
+ return
+ with open("./output.txt", "a") as fref:
+ fref.write(chunk)
+
+
+class VsperfController(vsperf_pb2_grpc.ControllerServicer):
+ """
+ Main Controller Class
+ """
+
+ def __init__(self):
+ """
+ Initialization
+ """
+ self.client = None
+ self.dut = None
+ self.dut_check = None
+ self.tgen_check = None
+ self.user = None
+ self.pwd = None
+ self.tgen_client = None
+ self.tgen = None
+ self.tgen_user = None
+ self.tgenpwd = None
+ self.tgen_conf = None
+ self.scenario = None
+ self.hpmax = None
+ self.hprequested = None
+ self.tgen_ip_address = None
+ self.trex_conf = None
+ # Default TGen is T-Rex
+ self.trex_conffile = "trex_cfg.yml"
+ self.collectd_conffile = "collectd.conf"
+
+ def setup(self):
+ """
+ Performs Setup of the client.
+ """
+ # Just connect to VM.
+ self.client = ssh.SSH(host=self.dut, user=self.user,
+ password=self.pwd)
+ self.client.wait()
+
+ def install_vsperf(self):
+ """
+ Perform actual installation
+ """
+ download_cmd = "git clone https://gerrit.opnfv.org/gerrit/vswitchperf"
+ self.client.run(download_cmd)
+ install_cmd = "cd vswitchperf/systems ; "
+ install_cmd += "echo '{}' | sudo -S ./build_base_machine.sh ".format(
+ self.pwd)
+ #install_cmd += "./build_base_machine.sh"
+ self.client.run(install_cmd)
+
+ def VsperfInstall(self, request, context):
+ """
+ Handle VSPERF install command from client
+ """
+ # print("Installing VSPERF")
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ vsperf_check_cmd = "source ~/vsperfenv/bin/activate ; cd vswitchperf* && ./vsperf --help"
+ vsperf_check_cmd_result = str(self.client.execute(vsperf_check_cmd)[1])
+ vsperf_verify_list = [
+ 'usage',
+ 'positional arguments',
+ 'optional arguments',
+ 'test selection options',
+ 'test behavior options']
+ for idx, i in enumerate(vsperf_verify_list, start=1):
+ if str(i) in vsperf_check_cmd_result:
+ if idx < 5:
+ continue
+ elif idx == 5:
+ return vsperf_pb2.StatusReply(
+ message="VSPERF is Already Installed on DUT-Host")
+ self.install_vsperf()
+ return vsperf_pb2.StatusReply(message="VSPERF Successfully Installed DUT-Host")
+
+ def HostConnect(self, request, context):
+ """
+ Handle host connectivity command from client
+ """
+ self.dut = request.ip
+ self.user = request.uname
+ self.pwd = request.pwd
+ self.setup()
+ check_cmd = "ls -l"
+ self.dut_check = int(self.client.execute(check_cmd)[0])
+ return vsperf_pb2.StatusReply(message="Successfully Connected")
+
+ def save_chunks_to_file(self, chunks, filename):
+ """
+ Write the output to file
+ """
+ with open(filename, 'wb') as fref:
+ for chunk in chunks:
+ fref.write(chunk.Content)
+
+###### Traffic Generator Related functions ####
+ def TGenHostConnect(self, request, context):
+ """
+ Connect to TGen-Node
+ """
+ self.tgen = request.ip
+ self.tgen_user = request.uname
+ self.tgenpwd = request.pwd
+ self.tgen_setup()
+ check_tgen_cmd = "ls"
+ self.tgen_check = int(self.tgen_client.execute(check_tgen_cmd)[0])
+ return vsperf_pb2.StatusReply(message="Successfully Connected")
+
+ def tgen_setup(self):
+ """
+ Setup the T-Gen Client
+ """
+ # Just connect to VM.
+ self.tgen_client = ssh.SSH(host=self.tgen, user=self.tgen_user,
+ password=self.tgenpwd)
+ self.tgen_client.wait()
+
+ def TGenInstall(self, request, context):
+ """
+ Install Traffic generator on the node.
+ """
+ if self.tgen_check != 0:
+ return vsperf_pb2.StatusReply(message="TGen-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " TGen-Host.")
+ kill_cmd = "pkill -f t-rex"
+ self.tgen_client.send_command(kill_cmd)
+ tgen_start_cmd = "cd trex_2.37/scripts && ./t-rex-64 -f cap2/dns.yaml -d 100 -m 1 --nc"
+ tgen_start_cmd_result = int(self.tgen_client.execute(tgen_start_cmd)[0])
+ kill_cmd = "pkill -f t-rex"
+ self.tgen_client.send_command(kill_cmd)
+ if tgen_start_cmd_result == 0:
+ return vsperf_pb2.StatusReply(
+ message="Traffic Generetor has T-rex Installed")
+ download_cmd = "git clone https://github.com/cisco-system-traffic-generator/trex-core"
+ self.tgen_client.run(download_cmd)
+ install_cmd = "cd trex-core/linux_dpdk ; ./b configure ; ./b build"
+ self.tgen_client.run(install_cmd)
+ # before you setup your trex_cfg.yml make sure to do sanity check
+ # NIC PICs and establish route between your DUT and Test Device.
+ return vsperf_pb2.StatusReply(message="Traffic Generetor has now T-rex Installed")
+
+ def TGenUploadConfigFile(self, request, context):
+ """
+ Handle upload config-file command from client
+ """
+ if self.tgen_check != 0:
+ return vsperf_pb2.StatusReply(message="TGen-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " TGen-Host.")
+ filename = self.trex_conffile
+ self.save_chunks_to_file(request, filename)
+ check_trex_config_cmd = "echo {} | sudo -S find /etc -maxdepth 1 -name trex_cfg.yaml".\
+ format(self.tgenpwd)
+ check_test_result = str(
+ self.tgen_client.execute(check_trex_config_cmd)[1])
+ if "trex_cfg.yaml" in check_test_result:
+ self.tgen_client.run("rm -f /etc/trex_cfg.yaml")
+ self.upload_tgen_config()
+ self.tgen_client.run(
+ "echo {} | sudo -S mv ~/trex_cfg.yaml /etc/".format(self.tgenpwd), pty=True)
+ return vsperf_pb2.UploadStatus(Message="Successfully Uploaded",
+ Code=1)
+
+ def upload_tgen_config(self):
+ """
+ Perform file upload.
+ """
+ self.tgen_client.put_file(self.trex_conffile, '/root/trex_cfg.yaml')
+
+# Tool-Chain related Functions####3
+
+ def install_collectd(self):
+ """
+ installation of the collectd
+ """
+ check_collectd_config_cmd = "find /opt -maxdepth 1 -name 'collectd'"
+ check_test_result = str(
+ self.client.execute(check_collectd_config_cmd)[1])
+ if "collectd" in check_test_result:
+ pass
+ else:
+ download_cmd = "git clone https://github.com/collectd/collectd.git"
+ self.client.run(download_cmd)
+ build_cmd = "cd collectd ; "
+ build_cmd += "./build.sh"
+ self.client.run(build_cmd)
+ config_cmd = "cd collectd ; ./configure --enable-syslog "
+ config_cmd += "--enable-logfile --enable-hugepages --enable-debug ; "
+ self.client.run(config_cmd)
+ install_cmd = "cd collectd ; make ; "
+ install_cmd += "echo '{}' | sudo -S make install".format(self.pwd)
+ self.client.run(install_cmd, pty=True)
+
+ def CollectdInstall(self, request, context):
+ """
+ Install Collectd on DUT
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ self.install_collectd()
+ return vsperf_pb2.StatusReply(
+ message="Collectd Successfully Installed on DUT-Host")
+
+ def upload_collectd_config(self):
+ """
+ Perform file upload.
+ """
+ self.client.put_file(self.collectd_conffile, '~/collectd.conf')
+ move_cmd = "echo '{}' | sudo -S mv ~/collectd.conf /opt/collectd/etc".format(
+ self.pwd)
+ self.client.run(move_cmd, pty=True)
+
+ def CollectdUploadConfig(self, request, context):
+ """
+ Upload collectd config-file on DUT
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ filename = self.collectd_conffile
+ self.save_chunks_to_file(request, filename)
+ self.upload_collectd_config()
+ return vsperf_pb2.UploadStatus(
+ Message="Successfully Collectd Configuration Uploaded", Code=1)
+
+###System Configuration related functions###
+
+ def DutHugepageConfig(self, request, context):
+ """
+ Configure the DUT system hugepage parameter from client
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ self.hpmax = int(request.hpmax)
+ self.hprequested = int(request.hprequested)
+ hugepage_cmd = "echo '{}' | sudo -S mkdir -p /mnt/huge ; ".format(
+ self.pwd)
+ hugepage_cmd += "echo '{}' | sudo -S mount -t hugetlbfs nodev /mnt/huge".format(
+ self.pwd)
+ self.client.run(hugepage_cmd, pty=True)
+ hp_nr_cmd = "cat /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages"
+ hp_free_cmd = "cat /sys/devices/system/node/node0/hugepages/hugepages-2048kB/free_hugepages"
+ hp_nr = int(self.client.execute(hp_nr_cmd)[1])
+ hp_free = int(self.client.execute(hp_free_cmd)[1])
+ if hp_free <= self.hprequested:
+ hp_nr_new = hp_nr + (self.hprequested - hp_free)
+ if hp_nr_new > self.hpmax:
+ hp_nr_new = self.hpmax
+
+ nr_hugepage_cmd = "echo '{}' | sudo -S bash -c \"echo 'vm.nr_hugepages={}' >>".\
+ format(self.pwd, hp_nr_new)
+ nr_hugepage_cmd += " /etc/sysctl.conf\""
+ self.client.run(nr_hugepage_cmd, pty=True)
+
+ dict_cmd = "cat /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages"
+ dict_check = int(self.client.execute(dict_cmd)[0])
+ if dict_check == 0:
+ node1_hugepage_cmd = "echo '{}' | sudo -s bash -c \"echo 0 >".format(self.pwd)
+ node1_hugepage_cmd += " /sys/devices/system/node/node1/"
+ node1_hugepage_cmd += "hugepages/hugepages-2048kB/nr_hugepages\""
+ return vsperf_pb2.StatusReply(
+ message="DUT-Host system configured with {} No of Hugepages".format(hp_nr_new))
+
+ def CheckDependecies(self, request, context):
+ """
+ Check and Install required packages on DUT
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ packages = ['python34-tkinter', 'sysstat', 'bc']
+ for pkg in packages:
+ # pkg_check_cmd = "dpkg -s {}".format(pkg) for ubuntu
+ pkg_check_cmd = "rpm -q {}".format(pkg)
+ pkg_cmd_response = self.client.execute(pkg_check_cmd)[0]
+ if pkg_cmd_response == 1:
+ install_pkg_cmd = "echo '{}' | sudo -S yum install -y {}".format(
+ self.pwd, pkg)
+ #install_pkg_cmd = "echo '{}' | sudo -S apt-get install -y {}".format(self.pwd,pkg)
+ self.client.run(install_pkg_cmd, pty=True)
+
+ return vsperf_pb2.StatusReply(message="Python34-tkinter, sysstat and bc Packages"\
+ "are now Installed")
+
+def serve():
+ """
+ Start servicing the client
+ """
+ server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
+ vsperf_pb2_grpc.add_ControllerServicer_to_server(
+ VsperfController(), server)
+ server.add_insecure_port('[::]:50051')
+ server.start()
+ try:
+ while True:
+ time.sleep(_ONE_DAY_IN_SECONDS)
+ except (SystemExit, KeyboardInterrupt, MemoryError, RuntimeError):
+ server.stop(0)
+
+
+if __name__ == "__main__":
+ serve()
diff --git a/tools/docker/deployment/interactive/docker-compose.yml b/tools/docker/deployment/interactive/docker-compose.yml
new file mode 100644
index 00000000..cbf894c5
--- /dev/null
+++ b/tools/docker/deployment/interactive/docker-compose.yml
@@ -0,0 +1,21 @@
+version: '2'
+
+services:
+ deploy:
+ build:
+ context: ./controller
+ volumes:
+ - ./controller/vsperf:/vsperf
+ ports:
+ - 50051:50051
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/docker/docs/architecture.txt b/tools/docker/docs/architecture.txt
new file mode 100644
index 00000000..a0b29e05
--- /dev/null
+++ b/tools/docker/docs/architecture.txt
@@ -0,0 +1,70 @@
+Architecture diagrams of the VSPERF-Containers.
+
+Figure-1: Deploy-Auto
+ ++++++++++++ +++++++++++++++++++++++++++
+ +Container + + +
+ + + + +
+ + + |--- + DUT - HOST +
+ + + | + +
+ + + | + +
+ + Deploy + | +++++++++++++++++++++++++++
+ + [AUTO] + --|
+ + + | +++++++++++++++++++++++++++
+ + + | + +
+ + + | + +
+ + + |--- + TGEN (HOST) +
+ + + + +
+ + + + +
+ ++++++++++++ +++++++++++++++++++++++++++
+
+Figure-2: Deploy-Interactive
+ ++++++++++++ ++++++++++++ +++++++++++++++++++++++++++
+ + + + Container+ + +
+ + + + + + +
+ + + + + |----+ DUT - HOST +
+ + + + + | + +
+ + + + + | + +
+ + CLIENT + + Deploy + | +++++++++++++++++++++++++++
+ + +<------>+ [INTER +---|
+ + + + ACTIVE]+ | +++++++++++++++++++++++++++
+ + + + + | + +
+ + + + + | + +
+ + + + + |----+ TGEN (HOST) +
+ + + + + + +
+ + + + + + +
+ ++++++++++++ ++++++++++++ +++++++++++++++++++++++++++
+
+Figure-3: TestControl Auto
+ ++++++++++++ +++++++++++++++++++++++++++ ++++++++++++
+ + Container+ + + +Container +
+ + + + + + +
+ + + |--- + DUT - HOST +----| + +
+ + + | + + | + +
+ + + | + + | + +
+ + Test + | +++++++++++++++++++++++++++ | + Results +
+ + Control +---| |---+ +
+ + [AUTO] + | +++++++++++++++++++++++++++ | + +
+ + + | + + | + +
+ + + | + + | + +
+ + + |--- + TGEN (HOST) +----| + +
+ + + + + + +
+ + + + + + +
+ ++++++++++++ +++++++++++++++++++++++++++ ++++++++++++
+
+Figure-4: TestControl Interactive
+ ++++++++++++ ++++++++++++ +++++++++++++++++++++++++++ ++++++++++++
+ + + +Container + + + + Container+
+ + + + + + + + +
+ + + + + |----+ DUT - HOST +----| + +
+ + + + + | + + | + +
+ + + + + | + + | + +
+ + CLIENT + + Test + | + + | + Results +
+ + + + Control + | +++++++++++++++++++++++++++ | + +
+ + +<------>+ [INTER +---| |---+ +
+ + + + ACTIVE]+ | +++++++++++++++++++++++++++ | + +
+ + + + + | + + | + +
+ + + + + | + + | + +
+ + + + + |----+ TGEN (HOST) +----| + +
+ + + + + + + + +
+ + + + + + + + +
+ ++++++++++++ ++++++++++++ +++++++++++++++++++++++++++ ++++++++++++
diff --git a/tools/docker/docs/client.rst b/tools/docker/docs/client.rst
new file mode 100644
index 00000000..1483ff40
--- /dev/null
+++ b/tools/docker/docs/client.rst
@@ -0,0 +1,99 @@
+VSPERF Client
+--------------
+VSPERF client is a simple python application, which can be used to work with interactive deploy and testcontrol containers.
+
+============
+Description
+============
+
+VSPERF client is used for both set-up of DUT-Host and TGen-Host as well as to run multiple tests. User can perform different operations by selecting the available options and their sub-options.
+
+VSPERF client provides following options to User.
+
+* Establish Connections
+This option allows user to initialize the connections.
+
+[0]Connect to DUT Host: It will establish connection with DUT-HOST. DUT-HOST refers to system where the DUT - vswitch and vnfs - run. The vsperf application also runs on DUT-HOST.
+[1]Connect to Tgen Host: This option will establish connection with TGEN-HOST. TGEN-HOST refers to system where the traffic generator runs. As of now, only T-Rex is support for installation and configuration.
+
+* Installation
+After establishing the connections, user can perform installations to set up the test environment. Under this, we have 3 options:
+
+[0]Install VSPERF : This option will first check either vsperf is installed on DUT-Host or not. If VSPERF is not installed, it will perform VSPERF installation process on DUT-Host
+
+[1]Install TGen: This option will check whether t-rex is installed on Tgen-host or not. If t-rex is already installed then it will also check either is working fine or not. If t-rex is not installed, then configured version of t-rex will be installed.
+
+[2]Install Collectd: This is option will install collectd on DUT-Host.
+
+* Upload Configuration Files
+Once the installation process is completed, User can upload configuration files. Two uploads are supported:
+
+[0]Upload TGen Configuration File: It will upload trex_cfg.yaml configuration file to Tgen-Host.[User can either specify path in vsperfclient.conf or provide path during runtime for the trex_cfg.yaml]. This file will be used to run T-rex traffic generator.
+
+[1]Upload Collectd Configuration File: This is option is use to uplaod collectd configuration file.
+
+* Manage DUT-System Configuration
+Following upload of configuration files, user can perform some basic configuration of the DUT-Host. The available options are:
+
+[0]DUT-Host hugepages configuration: This option allows User to manage hugepages of DUT-Host. [User need to provide values for HpMax and HpRequested in vsperfclient.conf]
+
+[1]Check VSPERF dependencies: Using this option user can check library dependencies on DUT-Host.
+
+* Run Test
+Once the above steps are completed, user can perform sanity checks and run the tests. The available options are:
+
+[0]Upload Test Configuration File : This option will upload the vsperf test configuration file.
+
+[1]Perform Sanity Checks before running tests : This option has certain sub-options, user must perform all sanity checks before running test. User may not able to start the Vsperf test until all sanity checks are passed. The sanity check option contains following sub-options: (a) check VSPERF is installed correctly, (b) check if VNF path is available on DUT-Host, (c) check if configured NIC-PCIs is available on TGen and DUT hosts (d) check if Collectd is installed correctly (e) check if connection between DUT-Host and TGen-Host is OK, (f) check CPU-allocation on DUT-host is done correctly.
+
+[2]Check if DUT-HOST is available : User can check if DUT-Host is available for Test or not. If DUT-Host is available for performing Vsperf user can go ahead and start performing test.
+
+[3]Start TGen : This option will start t-rex traffic generator for test.
+
+[4]Start Beats : This option will start beats on DUT-Host
+
+[5]Start Test : If all the sanity checks are passed, and traffic generator is running, then this option will start the vsperf test. Whatever test is defined in vsperfclient.conf will be performed. Note: User can also perform multiple tests.
+
+* Test Status
+Once user has started a test, he can check on the status. The following sub-options are available.
+
+[0]Test status : Check whether the test has completed successfully or failed. If user is running multiple tests, they can identify the failed test-name using this option.
+
+[1]Get Test Configuration file from DUT-host: User can also able to read the test configuration file content they uploaded.
+
+* Clean-Up
+When all tests are done, user can perform cleanup of the systems, using the following sub-options:
+
+[0]Remove VSPERF: This option will completely remove the vsperfenv on DUT-Host
+
+[1]Terminate VSPERF: This option will keep vsperfenv on DUT-Host. If there is any process still running related with the vsperf then this option will terminate all those processes like ovs-vswitchd,ovsdb-server,vppctl,stress,qemu-system-x86_64.
+
+[2]Remove Results from DUT-Host : This is option will remove all the test results located in /tmp folder.
+
+[3]Remove Uploaded Configuration Files: This option will remove all uploaded test configuration file
+
+[4]Remove Collectd: This option will uninstall collectd from the DUT-Host
+
+[5]Remove Everything: This option will execute all the options listed above.
+
+=============================
+How To Use
+=============================
+
+Prerequisites before running vsperf client
+^^^^^^^^^^^^^^^^^^^^^
+
+1. User must install grpcio, grpcio-tools and configparser for python3 environment.
+
+2. User has to prepare the client-configuration file by providing appropriate values.
+
+3. User has to prepare the configuration files that will be uploaded to either DUT-host or TGen-Host systems.
+
+4. T-rex and collectd configuration files should be named as trex_cfg.yaml and collectd.conf, respectively.
+
+5. Start the deployment-interactive container and testcontrol-interactive container, which will run the servers on ports 50051 and 50052, respectively.
+
+Run vsperf client
+^^^^^^^^^^^^^^^^^^^^^
+Locate and run the vsperf_client.py with python3.
+
diff --git a/tools/docker/docs/test.rst b/tools/docker/docs/test.rst
new file mode 100644
index 00000000..d002ddbe
--- /dev/null
+++ b/tools/docker/docs/test.rst
@@ -0,0 +1,86 @@
+Before using VSPERF client and VSPERF containers, user must run the prepare.sh script which will prepare their local environment.
+
+locate vsperf-docker/prepare.sh and run:
+bash prepare.sh
+
+VSPERF Containers
+------------------
+
+============
+deployment
+============
+Users have two choices for deployment, auto and interactive.
+
+1. auto
+^^^^^^^^^^^^^^^^^^^^^
+This auto deployment container will do everything related with VSPERF set-up automatically. It includes, installation of VSPERF, T-rex and collectd, uploading collectd configuration file on DUT-Host, uploading t-rex configuration files and starting the t-rex traffic generator. Before installing vsperf and t-rex, the container will perform verification process, which includes basic sanity checks such as checking for old installations, huge-page checks, necessary folders and software, etc. User should modify the t-rex(trex_cfg.yaml)and collectd(collectd.conf) configuration files depending on their needs before running the containers.
+
+
+Pre-Deployment Configuration
+******************
+User has to provide the following in list.env file:
+1.DUT-Host and TGen-Host related credentials and IP address
+2.Values for HUGEPAGE_MAX and HUGEPAGE_REQUESTED
+3.Option for sanity check - YES or NO.
+
+Build
+******************
+Use **docker-compose build** command to build the container.
+
+Run
+******************
+Run the container's service with **docker-compose run deploy** command.
+
+
+2. interactive
+^^^^^^^^^^^^^^^^^^^^^
+The interactive container must run before using the vsperf client. It will start the server on port 50051 for the vsperf client to send commands. Deployment interactive container handles all vsperf set-up related commands from vsperf client and performs the corresponding operation.
+
+
+Build
+******************
+Run **docker-compose build** command to build the container.
+
+Run
+******************
+Run the container with **docker-compose up deploy** command.
+Once the server is running user have to run testcontrol interactive container and then user can run the vsperf client.
+
+
+===============
+testcontrol
+===============
+For testcontrol too, user has two choices- auto and interactive.
+
+1. auto
+^^^^^^^^^^^^^^^^^^^^^
+This auto testcontrol container will perform test automatically on DUT-Host. This container also performing sanity checks automatically. User will also able to get test-status for all tests. If all sanity check doesn't satisfy then test will not run and container gracefully stopped. User can modify the vsperf.conf file which will be upload on DUT-Host automatically by container and used for performing the vsperf test.
+
+Pre-Deployment Configuration
+******************
+1.User have to provide all the DUT-Host credentials and IP address of TGen-host in list.env.
+2.Provide name for VSPERF_TESTS and VSPERF_CONFFILE in list.env.
+3.Provide option for VSPERF_TRAFFICGEN_MODE and CLEAN_UP [YES or NO] in list.env file.
+
+Build
+******************
+Run **docker-compose build** command to build the container.
+
+Run
+******************
+Run the container's service with **docker-compose run testcontrol** command.
+User can observe the results and perform the another test by just changing the VSPERF_TEST environment variable in list.env file.
+
+
+2. interactive
+^^^^^^^^^^^^^^^^^^^^^
+This interactive testcontrol container must run before using the vsperf client. It will start the server on port 50052 for the vsperf client. This testcontrol interactive container handle all the test related commands from vsperf client and do the operations. Testcontrol interactive container running server on localhost port 50052.
+
+Build
+******************
+Run **docker-compose build** command to build the container.
+
+Run
+******************
+Run the container with **docker-compose up testcontrol** command.
+After running this container user can use the vsperf client.
diff --git a/tools/docker/libs/proto/__init__.py b/tools/docker/libs/proto/__init__.py
new file mode 100644
index 00000000..ad0ebec3
--- /dev/null
+++ b/tools/docker/libs/proto/__init__.py
@@ -0,0 +1 @@
+#### Empty
diff --git a/tools/docker/libs/proto/vsperf.proto b/tools/docker/libs/proto/vsperf.proto
new file mode 100755
index 00000000..0fc45df3
--- /dev/null
+++ b/tools/docker/libs/proto/vsperf.proto
@@ -0,0 +1,109 @@
+// Copyright 2018-2019 .
+//
+// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+//
+// 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.
+
+
+syntax = "proto3";
+package vsperf;
+
+service Controller {
+ rpc HostConnect (HostInfo) returns (StatusReply) {}
+ rpc VsperfInstall (HostInfo) returns (StatusReply) {}
+ rpc TGenHostConnect (HostInfo) returns (StatusReply) {}
+ rpc TGenInstall (HostVerInfo) returns (StatusReply) {}
+ rpc TGenUploadConfigFile (stream ConfFile) returns (UploadStatus) {}
+ rpc CollectdInstall (HostInfo) returns (StatusReply) {}
+ rpc CollectdUploadConfig (stream ConfFile) returns (UploadStatus) {}
+ rpc DutHugepageConfig (HugepConf) returns (StatusReply) {}
+ rpc CheckDependecies (HostInfo) returns (StatusReply) {}
+ rpc UploadConfigFile (ConfFileTest) returns (UploadStatus) {}
+ rpc StartTest (ControlVsperf) returns (StatusReply) {}
+ rpc TestStatus (StatusQuery) returns (StatusReply) {}
+ rpc StartTGen (ControlTGen) returns (StatusReply) {}
+ rpc StartBeats (HostInfo) returns (StatusReply) {}
+ rpc RemoveVsperf (HostInfo) returns (StatusReply) {}
+ rpc RemoveResultFolder (HostInfo) returns (StatusReply) {}
+ rpc RemoveUploadedConfig (HostInfo) returns (StatusReply) {}
+ rpc RemoveCollectd (HostInfo) returns (StatusReply) {}
+ rpc RemoveEverything (HostInfo) returns (StatusReply) {}
+ rpc TerminateVsperf (HostInfo) returns (StatusReply) {}
+ rpc SanityNICCheck (HostInfo) returns (StatusReply) {}
+ rpc SanityCollectdCheck (HostInfo) returns (StatusReply) {}
+ rpc SanityVNFpath (HostInfo) returns (StatusReply) {}
+ rpc SanityVSPERFCheck (HostInfo) returns (StatusReply) {}
+ rpc SanityTgenConnDUTCheck (HostInfo) returns (StatusReply) {}
+ rpc SanityCPUAllocationCheck (HostInfo) returns (StatusReply) {}
+ rpc DUTvsperfTestAvailability (HostInfo) returns (StatusReply) {}
+ rpc GetVSPERFConffromDUT (HostInfo) returns (StatusReply) {}
+}
+
+message ControlVsperf {
+ string testtype = 1;
+ string conffile = 2;
+}
+
+message ControlTGen {
+ string params = 1;
+ string conffile = 2;
+}
+
+message LogDir {
+ string directory = 1;
+}
+
+message ConfFile {
+ bytes Content = 1;
+}
+
+message ConfFileTest {
+ string Content = 1;
+ string Filename = 2;
+}
+
+message HostInfo {
+ string ip = 1;
+ string uname = 2;
+ string pwd = 3;
+}
+
+message HugepConf {
+ string hpmax = 1;
+ string hprequested = 2;
+}
+
+message HostVerInfo {
+ string ip = 1;
+ string uname = 2;
+ string pwd = 3;
+ string version = 4;
+}
+
+message StatusQuery {
+ string testtype = 1;
+}
+
+message StatusReply {
+ string message = 1;
+}
+
+enum UploadStatusCode {
+ Unknown = 0;
+ Ok = 1;
+ Failed = 2;
+}
+
+message UploadStatus {
+ string Message = 1;
+ UploadStatusCode Code = 2;
+}
+
diff --git a/tools/docker/libs/utils/__init__.py b/tools/docker/libs/utils/__init__.py
new file mode 100644
index 00000000..ad0ebec3
--- /dev/null
+++ b/tools/docker/libs/utils/__init__.py
@@ -0,0 +1 @@
+#### Empty
diff --git a/tools/docker/libs/utils/exceptions.py b/tools/docker/libs/utils/exceptions.py
new file mode 100644
index 00000000..c4e0e097
--- /dev/null
+++ b/tools/docker/libs/utils/exceptions.py
@@ -0,0 +1,65 @@
+"""
+# Copyright (c) 2017 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+"""
+#pylint: disable=import-error
+from oslo_utils import excutils
+
+
+class VsperfCException(Exception):
+ """Base VSPERF-C Exception.
+
+ To correctly use this class, inherit from it and define
+ a 'message' property. That message will get printf'd
+ with the keyword arguments provided to the constructor.
+
+ Based on NeutronException class.
+ """
+ message = "An unknown exception occurred."
+
+ def __init__(self, **kwargs):
+ try:
+ super(VsperfCException, self).__init__(self.message % kwargs)
+ self.msg = self.message % kwargs
+ except Exception: # pylint: disable=broad-except
+ with excutils.save_and_reraise_exception() as ctxt:
+ if not self.use_fatal_exceptions():
+ ctxt.reraise = False
+ # at least get the core message out if something happened
+ super(VsperfCException, self).__init__(self.message)
+
+ def __str__(self):
+ return self.msg
+
+ def use_fatal_exceptions(self):
+ """Is the instance using fatal exceptions.
+
+ :returns: Always returns False.
+ """ #pylint: disable=no-self-use
+ return False
+
+
+class InvalidType(VsperfCException):
+ """Invalid type"""
+ message = 'Type "%(type_to_convert)s" is not valid'
+
+
+class SSHError(VsperfCException):
+ """ssh error"""
+ message = '%(error_msg)s'
+
+
+class SSHTimeout(SSHError):
+ """ssh timeout""" #pylint: disable=unnecessary-pass
+ pass
diff --git a/tools/docker/libs/utils/ssh.py b/tools/docker/libs/utils/ssh.py
new file mode 100644
index 00000000..a4df13b0
--- /dev/null
+++ b/tools/docker/libs/utils/ssh.py
@@ -0,0 +1,546 @@
+# Copyright 2013: Mirantis Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#pylint: disable=I,C,R,locally-disabled
+#pylint: disable=import-error,arguments-differ
+
+# this is a modified copy of rally/rally/common/sshutils.py
+
+"""High level ssh library.
+
+Usage examples:
+
+Execute command and get output:
+
+ ssh = sshclient.SSH("root", "example.com", port=33)
+ status, stdout, stderr = ssh.execute("ps ax")
+ if status:
+ raise Exception("Command failed with non-zero status.")
+ print(stdout.splitlines())
+
+Execute command with huge output:
+
+ class PseudoFile(io.RawIOBase):
+ def write(chunk):
+ if "error" in chunk:
+ email_admin(chunk)
+
+ ssh = SSH("root", "example.com")
+ with PseudoFile() as p:
+ ssh.run("tail -f /var/log/syslog", stdout=p, timeout=False)
+
+Execute local script on remote side:
+
+ ssh = sshclient.SSH("user", "example.com")
+
+ with open("~/myscript.sh", "r") as stdin_file:
+ status, out, err = ssh.execute('/bin/sh -s "arg1" "arg2"',
+ stdin=stdin_file)
+
+Upload file:
+
+ ssh = SSH("user", "example.com")
+ # use rb for binary files
+ with open("/store/file.gz", "rb") as stdin_file:
+ ssh.run("cat > ~/upload/file.gz", stdin=stdin_file)
+
+Eventlet:
+
+ eventlet.monkey_patch(select=True, time=True)
+ or
+ eventlet.monkey_patch()
+ or
+ sshclient = eventlet.import_patched("vsperf.ssh")
+
+"""
+from __future__ import print_function
+import io
+import logging
+import os
+import re
+import select
+import socket
+import time
+
+import paramiko
+from chainmap import ChainMap
+from oslo_utils import encodeutils
+from scp import SCPClient
+import six
+
+# When building container change this to
+import utils.exceptions as exceptions
+#else keep it as
+#import exceptions
+# When building container change this to
+from utils.utils import try_int, NON_NONE_DEFAULT, make_dict_from_map
+#else keep it as
+#from utils import try_int, NON_NONE_DEFAULT, make_dict_from_map
+
+
+def convert_key_to_str(key):
+ if not isinstance(key, (paramiko.RSAKey, paramiko.DSSKey)):
+ return key
+ k = io.StringIO()
+ key.write_private_key(k)
+ return k.getvalue()
+
+
+# class SSHError(Exception):
+# pass
+#
+#
+# class SSHTimeout(SSHError):
+# pass
+
+
+class SSH(object):
+ """Represent ssh connection."""
+ #pylint: disable=no-member
+
+ SSH_PORT = paramiko.config.SSH_PORT
+ DEFAULT_WAIT_TIMEOUT = 120
+
+ @staticmethod
+ def gen_keys(key_filename, bit_count=2048):
+ rsa_key = paramiko.RSAKey.generate(bits=bit_count, progress_func=None)
+ rsa_key.write_private_key_file(key_filename)
+ print("Writing %s ..." % key_filename)
+ with open('.'.join([key_filename, "pub"]), "w") as pubkey_file:
+ pubkey_file.write(rsa_key.get_name())
+ pubkey_file.write(' ')
+ pubkey_file.write(rsa_key.get_base64())
+ pubkey_file.write('\n')
+
+ @staticmethod
+ def get_class():
+ # must return static class name, anything else
+ # refers to the calling class
+ # i.e. the subclass, not the superclass
+ return SSH
+
+ @classmethod
+ def get_arg_key_map(cls):
+ return {
+ 'user': ('user', NON_NONE_DEFAULT),
+ 'host': ('ip', NON_NONE_DEFAULT),
+ 'port': ('ssh_port', cls.SSH_PORT),
+ 'pkey': ('pkey', None),
+ 'key_filename': ('key_filename', None),
+ 'password': ('password', None),
+ 'name': ('name', None),
+ }
+
+ def __init__(self, user, host, port=None, pkey=None,
+ key_filename=None, password=None, name=None):
+ """Initialize SSH client.
+
+ :param user: ssh username
+ :param host: hostname or ip address of remote ssh server
+ :param port: remote ssh port
+ :param pkey: RSA or DSS private key string or file object
+ :param key_filename: private key filename
+ :param password: password
+ """
+ self.name = name
+ if name:
+ self.log = logging.getLogger(__name__ + '.' + self.name)
+ else:
+ self.log = logging.getLogger(__name__)
+
+ self.wait_timeout = self.DEFAULT_WAIT_TIMEOUT
+ self.user = user
+ self.host = host
+ # everybody wants to debug this in the caller, do it here instead
+ self.log.debug("user:%s host:%s", user, host)
+
+ # we may get text port from YAML, convert to int
+ self.port = try_int(port, self.SSH_PORT)
+ self.pkey = self._get_pkey(pkey) if pkey else None
+ self.password = password
+ self.key_filename = key_filename
+ self._client = False
+ # paramiko loglevel debug will output ssh protocl debug
+ # we don't ever really want that unless we are debugging paramiko
+ # ssh issues
+ if os.environ.get("PARAMIKO_DEBUG", "").lower() == "true":
+ logging.getLogger("paramiko").setLevel(logging.DEBUG)
+ else:
+ logging.getLogger("paramiko").setLevel(logging.WARN)
+
+ @classmethod
+ def args_from_node(cls, node, overrides=None, defaults=None):
+ if overrides is None:
+ overrides = {}
+ if defaults is None:
+ defaults = {}
+
+ params = ChainMap(overrides, node, defaults)
+ return make_dict_from_map(params, cls.get_arg_key_map())
+
+ @classmethod
+ def from_node(cls, node, overrides=None, defaults=None):
+ return cls(**cls.args_from_node(node, overrides, defaults))
+
+ def _get_pkey(self, key):
+ if isinstance(key, six.string_types):
+ key = six.moves.StringIO(key)
+ errors = []
+ for key_class in (paramiko.rsakey.RSAKey, paramiko.dsskey.DSSKey):
+ try:
+ return key_class.from_private_key(key)
+ except paramiko.SSHException as e:
+ errors.append(e)
+ raise exceptions.SSHError(error_msg='Invalid pkey: %s' % errors)
+
+ @property
+ def is_connected(self):
+ return bool(self._client)
+
+ def _get_client(self):
+ if self.is_connected:
+ return self._client
+ try:
+ self._client = paramiko.SSHClient()
+ self._client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ self._client.connect(self.host, username=self.user,
+ port=self.port, pkey=self.pkey,
+ key_filename=self.key_filename,
+ password=self.password,
+ allow_agent=False, look_for_keys=False,
+ timeout=1)
+ return self._client
+ except Exception as e:
+ message = ("Exception %(exception_type)s was raised "
+ "during connect. Exception value is: %(exception)r" %
+ {"exception": e, "exception_type": type(e)})
+ self._client = False
+ raise exceptions.SSHError(error_msg=message)
+
+ def _make_dict(self):
+ return {
+ 'user': self.user,
+ 'host': self.host,
+ 'port': self.port,
+ 'pkey': self.pkey,
+ 'key_filename': self.key_filename,
+ 'password': self.password,
+ 'name': self.name,
+ }
+
+ def copy(self):
+ return self.get_class()(**self._make_dict())
+
+ def close(self):
+ if self._client:
+ self._client.close()
+ self._client = False
+
+ def run(self, cmd, stdin=None, stdout=None, stderr=None,
+ raise_on_error=True, timeout=3600,
+ keep_stdin_open=False, pty=False):
+ """Execute specified command on the server.
+
+ :param cmd: Command to be executed.
+ :type cmd: str
+ :param stdin: Open file or string to pass to stdin.
+ :param stdout: Open file to connect to stdout.
+ :param stderr: Open file to connect to stderr.
+ :param raise_on_error: If False then exit code will be return. If True
+ then exception will be raized if non-zero code.
+ :param timeout: Timeout in seconds for command execution.
+ Default 1 hour. No timeout if set to 0.
+ :param keep_stdin_open: don't close stdin on empty reads
+ :type keep_stdin_open: bool
+ :param pty: Request a pseudo terminal for this connection.
+ This allows passing control characters.
+ Default False.
+ :type pty: bool
+ """
+
+ client = self._get_client()
+
+ if isinstance(stdin, six.string_types):
+ stdin = six.moves.StringIO(stdin)
+
+ return self._run(client, cmd, stdin=stdin, stdout=stdout,
+ stderr=stderr, raise_on_error=raise_on_error,
+ timeout=timeout,
+ keep_stdin_open=keep_stdin_open, pty=pty)
+
+ def _run(self, client, cmd, stdin=None, stdout=None, stderr=None,
+ raise_on_error=True, timeout=3600,
+ keep_stdin_open=False, pty=False):
+
+ transport = client.get_transport()
+ session = transport.open_session()
+ if pty:
+ session.get_pty()
+ session.exec_command(cmd)
+ start_time = time.time()
+
+ # encode on transmit, decode on receive
+ data_to_send = encodeutils.safe_encode("", incoming='utf-8')
+ stderr_data = None
+
+ # If we have data to be sent to stdin then `select' should also
+ # check for stdin availability.
+ if stdin and not stdin.closed:
+ writes = [session]
+ else:
+ writes = []
+
+ while True:
+ # Block until data can be read/write.
+ e = select.select([session], writes, [session], 1)[2]
+
+ if session.recv_ready():
+ data = encodeutils.safe_decode(session.recv(4096), 'utf-8')
+ self.log.debug("stdout: %r", data)
+ if stdout is not None:
+ stdout.write(data)
+ continue
+
+ if session.recv_stderr_ready():
+ stderr_data = encodeutils.safe_decode(
+ session.recv_stderr(4096), 'utf-8')
+ self.log.debug("stderr: %r", stderr_data)
+ if stderr is not None:
+ stderr.write(stderr_data)
+ continue
+
+ if session.send_ready():
+ if stdin is not None and not stdin.closed:
+ if not data_to_send:
+ stdin_txt = stdin.read(4096)
+ if stdin_txt is None:
+ stdin_txt = ''
+ data_to_send = encodeutils.safe_encode(
+ stdin_txt, incoming='utf-8')
+ if not data_to_send:
+ # we may need to keep stdin open
+ if not keep_stdin_open:
+ stdin.close()
+ session.shutdown_write()
+ writes = []
+ if data_to_send:
+ sent_bytes = session.send(data_to_send)
+ # LOG.debug("sent: %s" % data_to_send[:sent_bytes])
+ data_to_send = data_to_send[sent_bytes:]
+
+ if session.exit_status_ready():
+ break
+
+ if timeout and (time.time() - timeout) > start_time:
+ message = ('Timeout executing command %(cmd)s on host %(host)s'
+ % {"cmd": cmd, "host": self.host})
+ raise exceptions.SSHTimeout(error_msg=message)
+ if e:
+ raise exceptions.SSHError(error_msg='Socket error')
+
+ exit_status = session.recv_exit_status()
+ if exit_status != 0 and raise_on_error:
+ fmt = "Command '%(cmd)s' failed with exit_status %(status)d."
+ details = fmt % {"cmd": cmd, "status": exit_status}
+ if stderr_data:
+ details += " Last stderr data: '%s'." % stderr_data
+ raise exceptions.SSHError(error_msg=details)
+ return exit_status
+
+ def execute(self, cmd, stdin=None, timeout=3600, raise_on_error=False):
+ """Execute the specified command on the server.
+
+ :param cmd: (str) Command to be executed.
+ :param stdin: (StringIO) Open file to be sent on process stdin.
+ :param timeout: (int) Timeout for execution of the command.
+ :param raise_on_error: (bool) If True, then an SSHError will be raised
+ when non-zero exit code.
+
+ :returns: tuple (exit_status, stdout, stderr)
+ """
+ stdout = six.moves.StringIO()
+ stderr = six.moves.StringIO()
+
+ exit_status = self.run(cmd, stderr=stderr,
+ stdout=stdout, stdin=stdin,
+ timeout=timeout, raise_on_error=raise_on_error)
+ stdout.seek(0)
+ stderr.seek(0)
+ return exit_status, stdout.read(), stderr.read()
+
+ def wait(self, timeout=None, interval=1):
+ """Wait for the host will be available via ssh."""
+ if timeout is None:
+ timeout = self.wait_timeout
+
+ end_time = time.time() + timeout
+ while True:
+ try:
+ return self.execute("uname")
+ except (socket.error, exceptions.SSHError) as e:
+ self.log.debug("Ssh is still unavailable: %r", e)
+ time.sleep(interval)
+ if time.time() > end_time:
+ raise exceptions.SSHTimeout(
+ error_msg='Timeout waiting for "%s"' % self.host)
+
+ def put(self, files, remote_path=b'.', recursive=False):
+ client = self._get_client()
+
+ with SCPClient(client.get_transport()) as scp:
+ scp.put(files, remote_path, recursive)
+
+ def get(self, remote_path, local_path='/tmp/', recursive=True):
+ client = self._get_client()
+
+ with SCPClient(client.get_transport()) as scp:
+ scp.get(remote_path, local_path, recursive)
+
+ # keep shell running in the background, e.g. screen
+ def send_command(self, command):
+ client = self._get_client()
+ client.exec_command(command, get_pty=True)
+
+ def _put_file_sftp(self, localpath, remotepath, mode=None):
+ client = self._get_client()
+
+ with client.open_sftp() as sftp:
+ sftp.put(localpath, remotepath)
+ if mode is None:
+ mode = 0o777 & os.stat(localpath).st_mode
+ sftp.chmod(remotepath, mode)
+
+ TILDE_EXPANSIONS_RE = re.compile("(^~[^/]*/)?(.*)")
+
+ def _put_file_shell(self, localpath, remotepath, mode=None):
+ # quote to stop wordpslit
+ tilde, remotepath = self.TILDE_EXPANSIONS_RE.match(remotepath).groups()
+ if not tilde:
+ tilde = ''
+ cmd = ['cat > %s"%s"' % (tilde, remotepath)]
+ if mode is not None:
+ # use -- so no options
+ cmd.append('chmod -- 0%o %s"%s"' % (mode, tilde, remotepath))
+
+ with open(localpath, "rb") as localfile:
+ # only chmod on successful cat
+ self.run("&& ".join(cmd), stdin=localfile)
+
+ def put_file(self, localpath, remotepath, mode=None):
+ """Copy specified local file to the server.
+
+ :param localpath: Local filename.
+ :param remotepath: Remote filename.
+ :param mode: Permissions to set after upload
+ """
+ try:
+ self._put_file_sftp(localpath, remotepath, mode=mode)
+ except (paramiko.SSHException, socket.error):
+ self._put_file_shell(localpath, remotepath, mode=mode)
+
+ def put_file_obj(self, file_obj, remotepath, mode=None):
+ client = self._get_client()
+
+ with client.open_sftp() as sftp:
+ sftp.putfo(file_obj, remotepath)
+ if mode is not None:
+ sftp.chmod(remotepath, mode)
+
+ def get_file_obj(self, remotepath, file_obj):
+ client = self._get_client()
+
+ with client.open_sftp() as sftp:
+ sftp.getfo(remotepath, file_obj)
+
+
+class AutoConnectSSH(SSH):
+
+ @classmethod
+ def get_arg_key_map(cls):
+ arg_key_map = super(AutoConnectSSH, cls).get_arg_key_map()
+ arg_key_map['wait'] = ('wait', True)
+ return arg_key_map
+
+ # always wait or we will get OpenStack SSH errors
+ def __init__(self, user, host, port=None, pkey=None,
+ key_filename=None, password=None, name=None, wait=True):
+ super(AutoConnectSSH, self).__init__(user, host, port, pkey,
+ key_filename, password, name)
+ if wait and wait is not True:
+ self.wait_timeout = int(wait)
+
+ def _make_dict(self):
+ data = super(AutoConnectSSH, self)._make_dict()
+ data.update({
+ 'wait': self.wait_timeout
+ })
+ return data
+
+ def _connect(self):
+ if not self.is_connected:
+ interval = 1
+ timeout = self.wait_timeout
+
+ end_time = time.time() + timeout
+ while True:
+ try:
+ return self._get_client()
+ except (socket.error, exceptions.SSHError) as e:
+ self.log.debug("Ssh is still unavailable: %r", e)
+ time.sleep(interval)
+ if time.time() > end_time:
+ raise exceptions.SSHTimeout(
+ error_msg='Timeout waiting for "%s"' % self.host)
+
+ def drop_connection(self):
+ """ Don't close anything, just force creation of a new client """
+ self._client = False
+
+ def execute(self, cmd, stdin=None, timeout=3600, raise_on_error=False):
+ self._connect()
+ return super(AutoConnectSSH, self).execute(cmd, stdin, timeout,
+ raise_on_error)
+
+ def run(self, cmd, stdin=None, stdout=None, stderr=None,
+ raise_on_error=True, timeout=3600,
+ keep_stdin_open=False, pty=False):
+ self._connect()
+ return super(AutoConnectSSH, self).run(cmd, stdin, stdout,
+ stderr, raise_on_error,
+ timeout, keep_stdin_open, pty)
+
+ def put(self, files, remote_path=b'.', recursive=False):
+ self._connect()
+ return super(AutoConnectSSH, self).put(files, remote_path, recursive)
+
+ def put_file(self, local_path, remote_path, mode=None):
+ self._connect()
+ return super(AutoConnectSSH, self).put_file(local_path,
+ remote_path, mode)
+
+ def put_file_obj(self, file_obj, remote_path, mode=None):
+ self._connect()
+ return super(AutoConnectSSH, self).put_file_obj(file_obj,
+ remote_path, mode)
+
+ def get_file_obj(self, remote_path, file_obj):
+ self._connect()
+ return super(AutoConnectSSH, self).get_file_obj(remote_path, file_obj)
+
+ @staticmethod
+ def get_class():
+ # must return static class name,
+ # anything else refers to the calling class
+ # i.e. the subclass, not the superclass
+ return AutoConnectSSH
diff --git a/tools/docker/libs/utils/utils.py b/tools/docker/libs/utils/utils.py
new file mode 100644
index 00000000..d945381e
--- /dev/null
+++ b/tools/docker/libs/utils/utils.py
@@ -0,0 +1,41 @@
+"""
+# Copyright 2013: Mirantis Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+"""
+
+
+NON_NONE_DEFAULT = object()
+
+
+def get_key_with_default(data, key, default=NON_NONE_DEFAULT):
+ """get default key"""
+ value = data.get(key, default)
+ if value is NON_NONE_DEFAULT:
+ raise KeyError(key)
+ return value
+
+
+def make_dict_from_map(data, key_map):
+ """mapping dict"""
+ return {dest_key: get_key_with_default(data, src_key, default)
+ for dest_key, (src_key, default) in key_map.items()}
+
+def try_int(s, *args):
+ """Convert to integer if possible."""
+ #pylint: disable=invalid-name
+ try:
+ return int(s)
+ except (TypeError, ValueError):
+ return args[0] if args else s
diff --git a/tools/docker/prepare.sh b/tools/docker/prepare.sh
new file mode 100755
index 00000000..7afdbd6b
--- /dev/null
+++ b/tools/docker/prepare.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+#This script will used to prepare local host to use vsperf client and containers.
+
+#first change the permission for prepare.sh file
+chmod a+x prepare.sh
+
+#Install python3 for local host
+sudo apt-get install python3
+
+#Install python3-pip
+sudo apt-get install python3-pip
+
+#Install grpcio, grpcio-tools and configparser
+pip3 install grpcio==1.4.0 grpcio-tools==1.4.0 configparser
+
+# Build .proto to create python library
+cd libs/proto && python3 -m grpc_tools.protoc -I./ --python_out=. --grpc_python_out=. vsperf.proto
+sed -i 's/import vsperf_pb2 as vsperf__pb2/from . import vsperf_pb2 as vsperf__pb2/g' vsperf_pb2_grpc.py
+cd ../..
+
+#copy libs/proto and libs/utils in deployment and testcontrol container at appropriate location.
+cp -r libs/proto deployment/interactive/controller/vsperf/proto
+cp -r libs/utils deployment/interactive/controller/vsperf/utils
+cp -r libs/proto testcontrol/interactive/controller/vsperf/proto
+cp -r libs/utils testcontrol/interactive/controller/vsperf/utils
+
+#copy libs/utils into deployment and testcontrol auto container at appropriate location.
+cp -r libs/utils deployment/auto/controller/vsperf/utils
+cp -r libs/utils testcontrol/auto/controller/vsperf/utils
+
+#copy libs/proto into client
+cp -r libs/proto client/proto
diff --git a/tools/docker/testcontrol/auto/controller/Dockerfile b/tools/docker/testcontrol/auto/controller/Dockerfile
new file mode 100644
index 00000000..4fbf7294
--- /dev/null
+++ b/tools/docker/testcontrol/auto/controller/Dockerfile
@@ -0,0 +1,23 @@
+FROM python:3.6
+LABEL maintainer="sridhar.rao@spirent.com"
+
+ENV GRPC_PYTHON_VERSION 1.4.0
+RUN apt-get update && apt-get -y install python3-pip && apt-get -y install openssh-server
+RUN pip3 install grpcio==${GRPC_PYTHON_VERSION} grpcio-tools==${GRPC_PYTHON_VERSION}
+RUN pip3 install paramiko
+RUN pip3 install chainmap
+RUN pip3 install oslo.utils
+RUN pip3 install scp
+
+WORKDIR /usr/src/app
+
+COPY ./vsperf ./vsperf
+
+VOLUME ["/usr/src/app/vsperf"]
+
+EXPOSE 50052
+
+CMD ["python3", "./vsperf/vsperf_controller.py"]
+
+#CMD tail -f /dev/null
+
diff --git a/tools/docker/testcontrol/auto/controller/list.env b/tools/docker/testcontrol/auto/controller/list.env
new file mode 100644
index 00000000..2883021b
--- /dev/null
+++ b/tools/docker/testcontrol/auto/controller/list.env
@@ -0,0 +1,13 @@
+DUT_IP_ADDRESS=10.10.120.24
+DUT_USERNAME=opnfv
+DUT_PASSWORD=opnfv
+
+TGEN_IP_ADDRESS=10.10.120.25
+
+VSPERF_TESTS=phy2phy_tput,pvp_tput
+VSPERF_CONFFILE=vsperf.conf
+
+VSPERF_TRAFFICGEN_MODE=NO
+
+CLEAN_UP=NO
+
diff --git a/tools/docker/testcontrol/auto/controller/vsperf/__init__.py b/tools/docker/testcontrol/auto/controller/vsperf/__init__.py
new file mode 100644
index 00000000..ad0ebec3
--- /dev/null
+++ b/tools/docker/testcontrol/auto/controller/vsperf/__init__.py
@@ -0,0 +1 @@
+#### Empty
diff --git a/tools/docker/testcontrol/auto/controller/vsperf/vsperf.conf b/tools/docker/testcontrol/auto/controller/vsperf/vsperf.conf
new file mode 100644
index 00000000..50d40f49
--- /dev/null
+++ b/tools/docker/testcontrol/auto/controller/vsperf/vsperf.conf
@@ -0,0 +1,21 @@
+VSWITCH_BRIDGE_NAME = 'vsperf-br0'
+WHITELIST_NICS = ['02:00.0', '02:00.1']
+TRAFFICGEN = 'Trex'
+TRAFFICGEN_TREX_HOST_IP_ADDR = '10.10.120.25'
+TRAFFICGEN_TREX_USER = 'root'
+TRAFFICGEN_TREX_BASE_DIR = '/root/trex_2.37/scripts/'
+TRAFFICGEN_TREX_LINE_SPEED_GBPS = '10'
+TRAFFICGEN_TREX_PORT1 = '0000:81:00.0'
+TRAFFICGEN_TREX_PORT2 = '0000:81:00.1'
+TRAFFICGEN_TREX_PROMISCUOUS = False
+TRAFFICGEN_DURATION=1
+TRAFFICGEN_LOSSRATE=0
+TRAFFICGEN_RFC2544_TESTS=10
+#TRAFFICGEN_PKT_SIZES=(64,128,256,512,1024,1280,1518)
+TRAFFICGEN_PKT_SIZES=(64,)
+GUEST_TESTPMD_FWD_MODE = ['io']
+GUEST_IMAGE = ['/home/opnfv/vnfs/vloop-vnf-ubuntu-18.04_20180920.qcow2']
+TRAFFICGEN_TREX_LATENCY_PPS = 1000
+TRAFFICGEN_TREX_RFC2544_BINARY_SEARCH_LOSS_VERIFICATION = True
+TRAFFICGEN_TREX_RFC2544_MAX_REPEAT = 2
+
diff --git a/tools/docker/testcontrol/auto/controller/vsperf/vsperf_controller.py b/tools/docker/testcontrol/auto/controller/vsperf/vsperf_controller.py
new file mode 100644
index 00000000..1b088fea
--- /dev/null
+++ b/tools/docker/testcontrol/auto/controller/vsperf/vsperf_controller.py
@@ -0,0 +1,469 @@
+# Copyright 2018-19 Spirent Communications.
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+"""
+VSPERF-controller
+"""
+
+# Fetching Environment Variable for controller, You can configure or
+# modifies list.env file for setting your environment variable.
+
+#pylint: disable=global-statement,no-else-continue
+#pylint: disable=too-many-branches
+
+import os
+import time
+import math
+import ast
+from utils import ssh
+
+_ONE_DAY_IN_SECONDS = 60 * 60 * 24
+TIMER = float()
+
+
+DUT_IP = os.getenv('DUT_IP_ADDRESS')
+DUT_USER = os.getenv('DUT_USERNAME')
+DUT_PWD = os.getenv('DUT_PASSWORD')
+
+TGEN_IP = os.getenv('TGEN_IP_ADDRESS')
+
+VSPERF_TEST = os.getenv('VSPERF_TESTS')
+VSPERF_CONF = os.getenv('VSPERF_CONFFILE')
+VSPERF_TRAFFICGEN_MODE = str(os.getenv('VSPERF_TRAFFICGEN_MODE'))
+
+START_COLLECTD = os.getenv('START_COLLECTD')
+START_BEATS = os.getenv('START_BEATS')
+CLEAN_UP = os.getenv('CLEAN_UP')
+
+DUT_CLIENT = None
+TGEN_CLIENT = None
+SANITY_CHECK_DONE_LIST = list()
+
+
+def host_connect():
+ """
+ Handle host connectivity to DUT
+ """
+ global DUT_CLIENT
+ DUT_CLIENT = ssh.SSH(host=DUT_IP, user=DUT_USER, password=DUT_PWD)
+ print("DUT Successfully Connected ..............................................[OK] \n ")
+
+def upload_test_config_file():
+ """
+ #Upload Test Config File on DUT
+ """
+ localpath = '/usr/src/app/vsperf/vsperf.conf'
+ if not os.path.exists(localpath):
+ print("VSPERF Test config File does not exists.......................[Failed]")
+ return
+ remotepath = '~/vsperf.conf'
+ check_test_config_cmd = "find ~/ -maxdepth 1 -name '{}'".format(
+ remotepath[2:])
+ check_test_result = str(DUT_CLIENT.execute(check_test_config_cmd)[1])
+ if remotepath[2:] in check_test_result:
+ DUT_CLIENT.run("rm -f {}".format(remotepath[2:]))
+ DUT_CLIENT.put_file(localpath, remotepath)
+ check_test_config_cmd_1= "find ~/ -maxdepth 1 -name '{}'".format(
+ remotepath[2:])
+ check_test_result_1= str(DUT_CLIENT.execute(check_test_config_cmd)[1])
+ if remotepath[2:] in check_test_result_1:
+ print(
+ "Test Configuration File Uploaded on DUT-Host.............................[OK] \n ")
+ else:
+ print("VSPERF Test config file upload failed.....................................[Critical]")
+
+def start_beats():
+ """
+ Start fileBeats on DUT
+ """
+ run_cmd = "echo '{}' | sudo -S service filebeat start".format(DUT_PWD)
+ DUT_CLIENT.run(run_cmd, pty=True)
+ print(
+ "Beats are started on DUT-Host............................................[OK] \n")
+
+def start_collectd():
+ """
+ start the collectd
+ """
+ run_cmd = "echo '{}' | sudo -S service collectd start".format(DUT_PWD)
+ DUT_CLIENT.run(run_cmd, pty=True)
+ print(
+ "Collectd is started on DUT-Host............................................[OK] \n")
+
+def run_vsperf_test():
+ """
+ Here we will perform the actual vsperf test
+ """
+ global TIMER
+ rmv_cmd = "cd /mnt/huge && echo {} | sudo -S rm -rf *".format(DUT_PWD)
+ DUT_CLIENT.run(rmv_cmd, pty=True)
+ cmd = "source ~/vsperfenv/bin/activate ; "
+ #cmd = "scl enable python33 bash ; "
+ cmd += "cd vswitchperf && "
+ cmd += "./vsperf "
+ if VSPERF_CONF:
+ cmd += "--conf-file ~/vsperf.conf "
+ if "yes" in VSPERF_TRAFFICGEN_MODE.lower():
+ cmd += "--mode trafficgen"
+ vsperf_test_list = VSPERF_TEST.split(",")
+ print(vsperf_test_list)
+ for test in vsperf_test_list:
+ atest = cmd
+ atest += test
+ DUT_CLIENT.run(atest, pty=True)
+ print(
+ "Test Successfully running................................................[OK]\n ")
+
+
+def test_status():
+ """
+ Chechk for the test status after performing test
+ """
+ testtype_list = VSPERF_TEST.split(",")
+ num_test = len(testtype_list)
+ test_success = []
+ test_failed = []
+ testtype_list_len = len(testtype_list)
+ for test in testtype_list:
+ passed_minutes = 5
+ latest_result_cmd = "find /tmp -mindepth 1 -type d -cmin -{} -printf '%f'".format(
+ passed_minutes)
+ test_result_dir = str(
+ (DUT_CLIENT.execute(latest_result_cmd)[1]).split('find')[0])
+ test_date_cmd = "date +%F"
+ test_date = str(DUT_CLIENT.execute(test_date_cmd)[1]).replace("\n", "")
+ if test_date in test_result_dir:
+ testcase_check_cmd = "cd /tmp && cd `ls -t | grep results | head"
+ testcase_check_cmd += " -{} | tail -1` && find . -maxdepth 1 -name '*{}*'".\
+ format(testtype_list_len, test)
+ testcase_check_output = str(
+ DUT_CLIENT.execute(testcase_check_cmd)[1]).split('\n', 2)
+ check = 0
+ for i in testcase_check_output:
+ if (".csv" in i) or (".md" in i) or (".rst" in i):
+ check += 1
+ if check == 3:
+ test_success.append(test)
+ else:
+ test_failed.append(test)
+ testtype_list_len -= 1
+ if num_test == len(test_success):
+ print("All Test Successfully Completed on DUT-Host Results... [OK]")
+ elif not test_success:
+ print("All Test Failed on DUT-Host \nResults... [Failed]")
+ else:
+ print(
+ "Only {} Test failed Results ... [Failed]\n"\
+ "All other Test Successfully Completed on DUT-Host Results... [OK] ".\
+ format(test_failed))
+
+
+def vsperf_remove():
+ """
+ Actual removal of the VSPERF
+ """
+ vsperf_rm_cmd = "echo '{}' | sudo -S rm -r ~/vswitchperf".format(DUT_PWD)
+ DUT_CLIENT.run(vsperf_rm_cmd)
+ vsperfenv_rm_cmd = "echo '{}' | sudo -S rm -r -f ~/vsperfenv".\
+ format(DUT_PWD)
+ DUT_CLIENT.run(vsperfenv_rm_cmd)
+
+
+def remove_uploaded_config():
+ """
+ Remove all the uploaded configuration files
+ """
+ vconfig_rm_cmd = "rm ~/vsperf.conf"
+ DUT_CLIENT.run(vconfig_rm_cmd)
+ cdconfig_rm_cmd = "echo '{}' | sudo -S rm /opt/collectd/etc/collectd.conf".\
+ format(DUT_PWD)
+ DUT_CLIENT.run(cdconfig_rm_cmd)
+
+
+def result_folders_remove():
+ """
+ Remove result folder on DUT
+ """
+ remove_cmd = "rm -r /tmp/*results*"
+ DUT_CLIENT.run(remove_cmd)
+
+
+def collectd_remove():
+ """
+ Remove collectd from DUT
+ """
+ collectd_dwn_rm_cmd = "echo '{}' | sudo -S rm -r -f ~/collectd".format(
+ DUT_PWD)
+ DUT_CLIENT.run(collectd_dwn_rm_cmd)
+ collectd_rm_cmd = "echo '{}' | sudo -S rm -r -f /opt/collectd".format(
+ DUT_PWD)
+ DUT_CLIENT.run(collectd_rm_cmd)
+
+
+def terminate_vsperf():
+ """
+ Terminate the VSPERF and kill processes
+ """
+ stress_kill_cmd = "echo '{}' | sudo -S pkill stress &> /dev/null".format(
+ DUT_PWD)
+ python3_kill_cmd = "echo '{}' | sudo -S pkill python3 &> /dev/null".format(
+ DUT_PWD)
+ qemu_kill_cmd = "echo '{}' | sudo -S killall -9 qemu-system-x86_64 &> /dev/null".format(
+ DUT_PWD)
+ DUT_CLIENT.run(stress_kill_cmd)
+ DUT_CLIENT.run(python3_kill_cmd)
+ DUT_CLIENT.run(qemu_kill_cmd)
+
+ # sometimes qemu resists to terminate, so wait a bit and kill it again
+ qemu_check_cmd = "pgrep qemu-system-x86_64"
+ qemu_cmd_response = DUT_CLIENT.execute(qemu_check_cmd)[1]
+
+ if qemu_cmd_response != '':
+ time.sleep(5)
+ DUT_CLIENT.run(qemu_kill_cmd)
+ time.sleep(5)
+
+ ovs_kill_cmd = "echo '{}' | sudo pkill ovs-vswitchd &> /dev/null".format(
+ DUT_PWD)
+ ovsdb_kill_cmd = "echo '{}' | sudo pkill ovsdb-server &> /dev/null".format(
+ DUT_PWD)
+ vppctl_kill_cmd = "echo '{}' | sudo pkill vppctl &> /dev/null".format(
+ DUT_PWD)
+ vpp_kill_cmd = "echo '{}' | sudo pkill vpp &> /dev/null".format(DUT_PWD)
+ vpp_cmd = "echo '{}' | sudo pkill -9 vpp &> /dev/null".format(DUT_PWD)
+
+ DUT_CLIENT.run(ovs_kill_cmd)
+ time.sleep(1)
+ DUT_CLIENT.run(ovsdb_kill_cmd)
+ time.sleep(1)
+ DUT_CLIENT.run(vppctl_kill_cmd)
+ time.sleep(1)
+ DUT_CLIENT.run(vpp_kill_cmd)
+ time.sleep(1)
+ DUT_CLIENT.run(vpp_cmd)
+ time.sleep(1)
+
+ print(
+ "All the VSPERF related process terminated successfully..............[OK]")
+
+
+def sanity_collectd_check():
+ """
+ Check and verify collectd is able to run and start properly
+ """
+ global SANITY_CHECK_DONE_LIST
+ check_collectd_cmd = "find /opt -maxdepth 1 -name 'collectd'"
+ check_test_result = str(DUT_CLIENT.execute(check_collectd_cmd)[1])
+ if "collectd" in check_test_result:
+ check_collectd_run_cmd = "echo {} | sudo -S service collectd start".format(
+ DUT_PWD)
+ DUT_CLIENT.run(check_collectd_run_cmd, pty=True)
+ check_collectd_status_cmd = "ps aux | grep collectd"
+ check_collectd_status = str(
+ DUT_CLIENT.execute(check_collectd_status_cmd)[1])
+ if "/sbin/collectd" in check_collectd_status:
+ SANITY_CHECK_DONE_LIST.append(int(1))
+ print(
+ "Collectd is working Fine ................................................[OK] \n ")
+ else:
+ print(
+ "Collectd Fail to Start, Install correctly before running Test....[Failed]\n ")
+ else:
+ print(
+ "Collectd is not installed yet........................................[Failed]\n")
+
+
+def sanity_vnf_path():
+ """
+ Check if VNF image is available on the configured path in Test Config File
+ """
+ # fetch the VNF path we placed in vsperf.conf file
+ global SANITY_CHECK_DONE_LIST
+ vsperf_conf_path = open('/usr/src/app/vsperf/vsperf.conf')
+ vsperf_conf_read = vsperf_conf_path.readlines()
+ for i in vsperf_conf_read:
+ if 'GUEST_IMAGE' in i:
+ vnf_image_path = i.split("'")[1]
+ vnf_path_check_cmd = "find {}".format(vnf_image_path)
+ vnf_path_check_result = str(
+ DUT_CLIENT.execute(vnf_path_check_cmd)[1])
+ if vnf_image_path in vnf_path_check_result:
+ SANITY_CHECK_DONE_LIST.append(int(2))
+ print(
+ "Test Configratuion file has Correct VNF path information on DUT-Host.." \
+ "...[OK]\n ")
+ else:
+ print(
+ "Test Configuration file has incorrect VNF path information......" \
+ "....[FAILED]\n")
+
+def sanity_vsperf_check():
+ """
+ We have to make sure that VSPERF is installed correctly
+ """
+ global SANITY_CHECK_DONE_LIST
+ vsperf_check_command = "source ~/vsperfenv/bin/activate ; cd vswitchperf* && ./vsperf --help"
+ vsperf_check_cmd_result = str(DUT_CLIENT.execute(vsperf_check_command)[1])
+ vsperf_verify_list = [
+ 'usage',
+ 'positional arguments',
+ 'optional arguments',
+ 'test selection options',
+ 'test behavior options']
+ for idx, i in enumerate(vsperf_verify_list, start=1):
+ if str(i) in vsperf_check_cmd_result:
+ if idx < 5:
+ continue
+ elif idx == 5:
+ SANITY_CHECK_DONE_LIST.append(int(3))
+ print("VSPERF Installed Correctly and Working fine......................." \
+ ".......[OK]\n")
+ else:
+ print(
+ "VSPERF DID Not Installed Correctly , INSTALL IT AGAIN...........[Critical]\n")
+ else:
+ print(
+ "VSPERF DID Not Installed Correctly , INSTALL IT AGAIN................[Critical]\n")
+ break
+
+def variable_from_test_config(aparameter):
+ """This function can be use to read any configuration paramter from vsperf.conf"""
+ read_cmd = 'cat ~/vsperf.conf | grep "{}"'.format(aparameter)
+ read_cmd_output = str(DUT_CLIENT.execute(read_cmd)[1])
+ print(read_cmd_output)
+ if not read_cmd_output or '#' in read_cmd_output:
+ return 0
+ return read_cmd_output.split("=")[1].strip()
+
+def cpumask2coreids(mask):
+ """conver mask to coreids"""
+ intmask = int(mask, 16)
+ i = 1
+ coreids = []
+ while i < intmask:
+ if i & intmask:
+ coreids.append(str(math.frexp(i)[1]-1))
+ i = i << 1
+ return coreids
+
+def sanity_cpu_allocation_check():
+ """It will check the cpu allocation before run test"""
+ global SANITY_CHECK_DONE_LIST
+ read_setting_cmd = "source vsperfenv/bin/activate ; cd vswitchperf* && "
+ read_setting_cmd += './vsperf --list-settings'
+ default_vsperf_settings = ast.literal_eval(str(DUT_CLIENT.execute(read_setting_cmd)[1]))
+ default_cpu_map = default_vsperf_settings["VSWITCH_VHOST_CPU_MAP"]
+ default_vswitch_pmd_cpu_mask = str(default_vsperf_settings["VSWITCH_PMD_CPU_MASK"])
+ default_vswitch_vhost_cpu_map = [str(x) for x in default_cpu_map]
+ vswitch_pmd_cpu_mask = variable_from_test_config("VSWITCH_PMD_CPU_MASK")
+ vswitch_cpu_map = (variable_from_test_config("VSWITCH_VHOST_CPU_MAP"))
+ vswitch_vhost_cpu_map = 0
+ if vswitch_cpu_map != 0:
+ vswitch_vhost_cpu_map = [str(x) for x in ast.literal_eval(vswitch_cpu_map)]
+
+ if vswitch_pmd_cpu_mask == 0 and vswitch_vhost_cpu_map == 0:
+ print("CPU allocation Check Done,"\
+ "\nNo vswitch_pmd_cpu_mask or vswitch_vhost_cpu_map assign in test config file\n" \
+ "Using Default Settings ..................................................[OK]\n")
+ elif vswitch_pmd_cpu_mask != 0 and vswitch_vhost_cpu_map == 0:
+ core_id = cpumask2coreids(vswitch_pmd_cpu_mask)
+ print(core_id)
+ if len(default_vswitch_vhost_cpu_map) >= len(core_id):
+ if all(elem in default_vswitch_vhost_cpu_map for elem in core_id):
+ print("CPU allocation properly done on DUT-Host.................[OK]\n")
+ else:
+ print("CPU allocation not done properly on DUT-Host............[Failed]\n")
+ else:
+ print("CPU allocation not done properly on DUT-Host............[Failed]\n")
+ elif vswitch_pmd_cpu_mask == 0 and vswitch_vhost_cpu_map != 0:
+ core_id_1 = cpumask2coreids(default_vswitch_pmd_cpu_mask)
+ print(core_id_1)
+ if len(vswitch_vhost_cpu_map) >= len(core_id_1):
+ if all(elem in vswitch_vhost_cpu_map for elem in core_id_1):
+ print("CPU allocation properly done on DUT-Host.................[OK]\n")
+ else:
+ print("CPU allocation not done properly on DUT-Host............[Failed]\n")
+ else:
+ print("CPU allocation not done properly on DUT-Host............[Failed]\n")
+ else:
+ core_id_2 = cpumask2coreids(vswitch_pmd_cpu_mask)
+ print(core_id_2)
+ if len(vswitch_vhost_cpu_map) >= len(core_id_2):
+ if all(elem in vswitch_vhost_cpu_map for elem in core_id_2):
+ print("CPU allocation properly done on DUT-Host.................[OK]\n")
+ else:
+ print("CPU allocation not done properly on DUT-Host............[Failed]\n")
+ else:
+ print("CPU allocation not done properly on DUT-Host............[Failed]\n")
+
+
+
+def sanity_dut_conn_tgen_check():
+ """
+ We should confirm the DUT connectivity with the Tgen and Traffic Generator is working or not
+ """
+ global SANITY_CHECK_DONE_LIST
+ tgen_connectivity_check_cmd = "ping {} -c 1".format(TGEN_IP)
+ tgen_connectivity_check_result = int(DUT_CLIENT.execute(tgen_connectivity_check_cmd)[0])
+ if tgen_connectivity_check_result == 0:
+ SANITY_CHECK_DONE_LIST.append(int(5))
+ print(
+ "DUT-Host is successfully reachable to Traffic Generator Host.............[OK]\n")
+ else:
+ print(
+ "DUT-host is unsuccessful to reach the Traffic Generator Host.............[Failed]")
+ print(
+ "Make sure to establish connection before running Test...............[Critical]\n")
+
+if DUT_IP:
+ host_connect()
+if not DUT_CLIENT:
+ print('Failed to connect to DUT ...............[Critical]')
+ sys.exit()
+else:
+ upload_test_config_file()
+ sanity_vnf_path()
+ sanity_cpu_allocation_check()
+ sanity_collectd_check()
+ sanity_vsperf_check()
+ sanity_dut_conn_tgen_check()
+ if "yes" in START_COLLECTD.lower():
+ start_collectd()
+ if "yes" in START_BEATS.lower():
+ start_beats()
+
+if 'v' in VSPERF_TEST:
+ if len(SANITY_CHECK_DONE_LIST) != 4:
+ print("Certain Sanity Checks Failed\n" \
+ "You can make changes based on the outputs and run" \
+ "the testcontrol auto container again")
+ else:
+ run_vsperf_test()
+ test_status()
+else:
+ if len(SANITY_CHECK_DONE_LIST) != 3:
+ print("Certain Sanity Checks Failed\n" \
+ "You can make changes based on the outputs and run" \
+ "the testcontrol auto container again")
+ else:
+ run_vsperf_test()
+ test_status()
+
+
+if "yes" in CLEAN_UP.lower():
+ vsperf_remove()
+ remove_uploaded_config()
+ result_folders_remove()
+ collectd_remove()
+ terminate_vsperf()
diff --git a/tools/docker/testcontrol/auto/docker-compose.yml b/tools/docker/testcontrol/auto/docker-compose.yml
new file mode 100644
index 00000000..50c528a6
--- /dev/null
+++ b/tools/docker/testcontrol/auto/docker-compose.yml
@@ -0,0 +1,22 @@
+version: '2'
+
+services:
+ testcontrol:
+ build:
+ context: ./controller
+ volumes:
+ - ./controller/vsperf:/vsperf
+ env_file:
+ - ./controller/list.env
+ ports:
+ - 50052
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/docker/testcontrol/interactive/controller/Dockerfile b/tools/docker/testcontrol/interactive/controller/Dockerfile
new file mode 100644
index 00000000..16cf59fd
--- /dev/null
+++ b/tools/docker/testcontrol/interactive/controller/Dockerfile
@@ -0,0 +1,22 @@
+FROM python:3.6
+LABEL maintainer="sridhar.rao@spirent.com"
+
+ENV GRPC_PYTHON_VERSION 1.4.0
+RUN apt-get update && apt-get -y install python3-pip
+RUN pip3 install grpcio==${GRPC_PYTHON_VERSION} grpcio-tools==${GRPC_PYTHON_VERSION}
+RUN pip3 install paramiko
+RUN pip3 install chainmap
+RUN pip3 install oslo.utils
+RUN pip3 install scp
+
+WORKDIR /usr/src/app
+
+COPY ./vsperf ./vsperf
+
+VOLUME ["/usr/src/app/vsperf"]
+
+EXPOSE 50052
+
+CMD ["python3", "./vsperf/vsperf_controller.py"]
+
+#CMD tail -f /dev/null
diff --git a/tools/docker/testcontrol/interactive/controller/vsperf/__init__.py b/tools/docker/testcontrol/interactive/controller/vsperf/__init__.py
new file mode 100644
index 00000000..ad0ebec3
--- /dev/null
+++ b/tools/docker/testcontrol/interactive/controller/vsperf/__init__.py
@@ -0,0 +1 @@
+#### Empty
diff --git a/tools/docker/testcontrol/interactive/controller/vsperf/output.txt b/tools/docker/testcontrol/interactive/controller/vsperf/output.txt
new file mode 100644
index 00000000..912c877b
--- /dev/null
+++ b/tools/docker/testcontrol/interactive/controller/vsperf/output.txt
@@ -0,0 +1 @@
+[INFO ] 2019-08-27 18:09:46,085 : (root) - Overall test report written to "/tmp/results_2019-08-27_18-08-53/OvsDpdkVhost_test_report.rst"
diff --git a/tools/docker/testcontrol/interactive/controller/vsperf/vsperf_controller.py b/tools/docker/testcontrol/interactive/controller/vsperf/vsperf_controller.py
new file mode 100644
index 00000000..d1c3838d
--- /dev/null
+++ b/tools/docker/testcontrol/interactive/controller/vsperf/vsperf_controller.py
@@ -0,0 +1,706 @@
+# Copyright 2018-19 Spirent Communications.
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+# pylint: disable=R0904
+# pylint: disable=R0902
+# twenty-two is reasonable in this script
+
+"""
+VSPER docker-controller.
+"""
+
+import io
+import time
+import ast
+import math
+
+from concurrent import futures
+
+import grpc
+from proto import vsperf_pb2
+from proto import vsperf_pb2_grpc
+from utils import ssh
+
+_ONE_DAY_IN_SECONDS = 60 * 60 * 24
+
+
+# pylint: disable=too-few-public-methods,no-self-use
+class PseudoFile(io.RawIOBase):
+ """
+ Handle ssh command output.
+ """
+
+ def write(self, chunk):
+ """
+ Write to file
+ """
+ if "error" in chunk:
+ return
+ with open("./output.txt", "w") as fref:
+ fref.write(chunk)
+
+
+class VsperfController(vsperf_pb2_grpc.ControllerServicer):
+ """
+ Main Controller Class
+ """
+
+ def __init__(self):
+ """
+ Initialization
+ """
+ self.client = None
+ self.dut_check = None
+ self.dut = None
+ self.user = None
+ self.pwd = None
+ self.vsperf_conf = None
+ self.tgen_client = None
+ self.tgen_check = None
+ self.tgen = None
+ self.tgen_user = None
+ self.tgenpwd = None
+ self.tgen_conf = None
+ self.scenario = None
+ self.testcase = None
+ self.tgen_ip_address = None
+ self.testtype = None
+ self.trex_conf = None
+ self.trex_params = None
+ self.conffile = None
+ self.tests_run_check = None
+ self.tgen_start_check = None
+ # Default TGen is T-Rex
+ self.trex_conffile = "trex_cfg.yml"
+ self.collectd_conffile = "collectd.conf"
+ self.test_upload_check = 0
+ self.sanity_check_done_list = list()
+
+ def setup(self):
+ """
+ Performs Setup of the client.
+ """
+ # Just connect to VM.
+ self.client = ssh.SSH(host=self.dut, user=self.user,
+ password=self.pwd)
+ self.client.wait()
+
+ def upload_config(self):
+ """
+ Perform file upload.
+ """
+ # self.client._put_file_shell(self.conffile, '~/vsperf.conf')
+ self.client.put_file(self.conffile, '~/{}'.format(self.conffile))
+ print("No")
+
+ def run_test(self):
+ """
+ Run test
+ """
+ # Sometimes hugepage store in /mnt/huge in order to free up the
+ # hugepage removing this stored hugepage is necessory
+ rmv_cmd = "cd /mnt/huge && echo {} | sudo -S rm -rf *".format(self.pwd)
+ self.client.run(rmv_cmd, pty=True)
+ cmd = "source ~/vsperfenv/bin/activate ; "
+ #cmd = "scl enable python33 bash ; "
+ cmd += "cd vswitchperf* && "
+ cmd += "./vsperf "
+ if self.vsperf_conf:
+ cmd += "--conf-file ~/{} ".format(self.conffile)
+ # cmd += self.conffile
+ cmd += self.scenario
+ with PseudoFile() as pref:
+ self.client.run(cmd, stdout=pref, pty=True, timeout=0)
+
+ def TestStatus(self, request, context):
+ """
+ Chechk for the test status after performing test
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ if self.tests_run_check != 1:
+ return vsperf_pb2.StatusReply(message="No test have ran yet. [!]")
+ testtype_list = request.testtype.split(",")
+ test_success = []
+ test_failed = []
+ testtype_list_len = len(testtype_list)
+ for test in testtype_list:
+ #latest_result_cmd = "find /tmp -mindepth 1 -type d -cmin -5 -printf '%f'"
+ test_result_dir = str((self.client.\
+ execute("find /tmp -mindepth 1 -type d -cmin -5 -printf '%f'")[1]).\
+ split('find')[0])
+ #test_date_cmd = "date +%F"
+ test_date = str(self.client.execute("date +%F")[1]).replace("\n", "")
+ if test_date in test_result_dir:
+ testcase_check_cmd = "cd /tmp && cd `ls -t | grep results | head -{} | tail -1`".\
+ format(testtype_list_len)
+ testcase_check_cmd += " && find . -maxdepth 1 -name '*{}*'".\
+ format(test)
+ testcase_check_output = str(self.client.execute(testcase_check_cmd)[1]).\
+ split('\n', 2)
+ check = 0
+ for i in testcase_check_output:
+ if (".csv" in i) or (".md" in i) or (".rst" in i):
+ check += 1
+ if check == 3:
+ test_success.append(test)
+ else:
+ test_failed.append(test)
+ testtype_list_len -= 1
+ if len(testtype_list) == len(test_success):
+ return vsperf_pb2.StatusReply(message="All Test Successfully Completed on DUT-Host" \
+ "\nResults... [OK]")
+ if not test_success:
+ return vsperf_pb2.StatusReply(
+ message="All Test Failed on DUT-Host \nResults... [Failed]")
+ return vsperf_pb2.StatusReply(message="Only {} Test failed Results ... [Failed]\n"\
+ "All other Test Successfully Completed on DUT-Host Results... [OK] ".\
+ format(test_failed))
+
+ def HostConnect(self, request, context):
+ """
+ Handle host connectivity command from client
+ """
+ self.dut = request.ip
+ self.user = request.uname
+ self.pwd = request.pwd
+ self.setup()
+ check_cmd = "ls -l"
+ self.dut_check = int(self.client.execute(check_cmd)[0])
+ return vsperf_pb2.StatusReply(message="Successfully Connected")
+
+ def save_chunks_to_file(self, chunks, filename):
+ """
+ Write the output to file
+ """
+ with open(filename, 'w+') as fref:
+ fref.write(chunks)
+
+ def UploadConfigFile(self, request, context):
+ """
+ Handle upload config-file command from client
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ chunks = request.Content
+ filename = request.Filename
+ self.conffile = filename
+ self.save_chunks_to_file(chunks, filename)
+ # This is chechking if vsperf.conf already exist first remove that and
+ # then upload the new file.
+ check_test_config_cmd = "find ~/ -maxdepth 1 -name {}".format(filename)
+ check_test_result = str(self.client.execute(check_test_config_cmd)[1])
+ if "{}".format(filename) in check_test_result:
+ self.client.run("rm -f {}".format(filename))
+ self.upload_config()
+ self.test_upload_check = 1
+ print("Hello")
+ return vsperf_pb2.UploadStatus(Message="Successfully Uploaded", Code=1)
+
+ def StartTest(self, request, context):
+ """
+ Handle start-test command from client
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ sanity_dict = {1:"Check installed VSPERF",
+ 2:"Check Test Config's VNF path is available on DUT-Host",
+ 3:"Check NIC PCIs is available on Traffic Generator",
+ 4:"Check CPU allocation on DUT-Host",
+ 5:"Check installed Collectd",
+ 6:"Check Connection between DUT-Host and Traffic Generator Host"}
+ sanity_dict_option_list = list(sanity_dict.keys())
+ remaining_sanity = [item for item in sanity_dict_option_list if item not in \
+ self.sanity_check_done_list]
+ if remaining_sanity:
+ sanity_return_msg = ""
+ for i_sanity in remaining_sanity:
+ sanity_return_msg += sanity_dict[i_sanity] + "\n"
+ return vsperf_pb2.StatusReply(message="The following sanity checks are either not"\
+ " performed yet or Does not satisfy test requirements" \
+ "\n{}".format(sanity_return_msg))
+ if self.test_upload_check == 0:
+ return vsperf_pb2.StatusReply(message="Test File is not uploaded yet [!] " \
+ "\nUpload Test Configuration File.")
+ if self.tgen_start_check != 1:
+ return vsperf_pb2.StatusReply(message="Traffic Generator has not started yet [!]")
+ self.vsperf_conf = request.conffile
+ self.testtype = request.testtype
+ testtype_list = self.testtype.split(",")
+ self.tests_run_check = 1
+ for test in testtype_list:
+ self.scenario = test
+ self.run_test()
+ return vsperf_pb2.StatusReply(message="Test Successfully Completed")
+
+###### Traffic Generator Related functions ####
+ def TGenHostConnect(self, request, context):
+ """
+ Connect to TGen-Node
+ """
+ self.tgen = request.ip
+ self.tgen_user = request.uname
+ self.tgenpwd = request.pwd
+ self.tgen_setup()
+ check_tgen_cmd = "ls"
+ self.tgen_check = int(self.tgen_client.execute(check_tgen_cmd)[0])
+ return vsperf_pb2.StatusReply(message="Successfully Connected")
+
+ def tgen_setup(self):
+ """
+ Setup the T-Gen Client
+ """
+ # Just connect to VM.
+ self.tgen_client = ssh.SSH(host=self.tgen, user=self.tgen_user,
+ password=self.tgenpwd)
+ self.tgen_client.wait()
+
+ def StartBeats(self, request, context):
+ """
+ Start fileBeats on DUT
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ run_cmd = "echo '{}' | sudo -S service filebeat start".format(self.pwd)
+ #run_cmd = "sudo service filebeat start"
+ self.client.run(run_cmd, pty=True)
+ return vsperf_pb2.StatusReply(message="Beats are started on DUT-Host")
+
+ def DUTvsperfTestAvailability(self, request, context):
+ """
+ Before running test we have to make sure there is no other test running
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ vsperf_ava_cmd = "ps -ef | grep -v grep | grep ./vsperf | awk '{print $2}'"
+ vsperf_ava_result = len((self.client.execute(vsperf_ava_cmd)[1]).split("\n"))
+ if vsperf_ava_result == 1:
+ return vsperf_pb2.StatusReply(message="DUT-Host is available for performing" \
+ " VSPERF Test\nYou can perform Test!")
+ return vsperf_pb2.StatusReply(message="DUT-Host is busy right now, Wait for some time\n\
+ Always Check availability before Running Test!")
+
+
+###Clean-UP process related functions####
+
+
+ def vsperf_remove(self):
+ """
+ Actual removal of the VSPERF
+ """
+ vsperf_rm_cmd = "echo '{}' | sudo -S rm -r ~/vswitchperf".format(
+ self.pwd)
+ self.client.run(vsperf_rm_cmd, pty=True)
+ vsperfenv_rm_cmd = "echo '{}' | sudo -S rm -r -f ~/vsperfenv".format(
+ self.pwd)
+ self.client.run(vsperfenv_rm_cmd, pty=True)
+
+ def remove_uploaded_config(self):
+ """
+ Remove all the uploaded test configuration file
+ """
+ vconfig_rm_cmd = "rm ~/{}".format(self.conffile)
+ self.client.run(vconfig_rm_cmd, pty=True)
+
+ def result_folder_remove(self):
+ """
+ Remove result folder on DUT
+ """
+ remove_cmd = "rm -r /tmp/*results*"
+ self.client.run(remove_cmd, pty=True)
+
+ def collectd_remove(self):
+ """
+ Remove collectd from DUT
+ """
+ collectd_dwn_rm_cmd = "echo '{}' | sudo -S rm -r -f ~/collectd".format(
+ self.pwd)
+ self.client.run(collectd_dwn_rm_cmd, pty=True)
+ collectd_rm_cmd = "echo '{}' | sudo -S rm -r -f /opt/collectd".format(
+ self.pwd)
+ self.client.run(collectd_rm_cmd, pty=True)
+
+ def RemoveVsperf(self, request, context):
+ """
+ Handle VSPERF removal command from client
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ self.vsperf_remove()
+ return vsperf_pb2.StatusReply(message="Successfully VSPERF Removed")
+
+ def TerminateVsperf(self, request, context):
+ """
+ Terminate the VSPERF and kill processes
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ stress_kill_cmd = "pkill stress"
+ python3_kill_cmd = "pkill python3"
+ qemu_kill_cmd = "killall -9 qemu-system-x86_64"
+ self.client.send_command(stress_kill_cmd)
+ self.client.send_command(python3_kill_cmd)
+ self.client.send_command(qemu_kill_cmd)
+
+ # sometimes qemu resists to terminate, so wait a bit and kill it again
+ qemu_check_cmd = "pgrep qemu-system-x86_64"
+ qemu_cmd_response = self.client.execute(qemu_check_cmd)[1]
+
+ if qemu_cmd_response != '':
+ time.sleep(5)
+ self.client.send_command(qemu_kill_cmds)
+ time.sleep(5)
+
+ ovs_kill_cmd = "pkill ovs-vswitchd"
+ ovsdb_kill_cmd = "pkill ovsdb-server"
+ vppctl_kill_cmd = "pkill vppctl"
+ vpp_kill_cmd = "pkill vpp"
+ vpp_cmd = "pkill -9".format(self.pwd)
+
+ self.client.send_command(ovs_kill_cmd)
+ time.sleep(1)
+ self.client.send_command(ovsdb_kill_cmd)
+ time.sleep(1)
+ self.client.send_command(vppctl_kill_cmd)
+ time.sleep(1)
+ self.client.send_command(vpp_kill_cmd)
+ time.sleep(1)
+ self.client.send_command(vpp_cmd)
+ time.sleep(1)
+
+ return vsperf_pb2.StatusReply(
+ message="All the VSPERF related process terminated successfully")
+
+ def RemoveResultFolder(self, request, context):
+ """
+ Handle result folder removal command from client
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ self.result_folder_remove()
+ return vsperf_pb2.StatusReply(
+ message="Successfully VSPERF Results Removed")
+
+ def RemoveUploadedConfig(self, request, context):
+ """
+ Handle all configuration file removal command from client
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ if self.tgen_check != 0:
+ return vsperf_pb2.StatusReply(message="TGen-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " TGen-Host.")
+ if self.test_upload_check == 0:
+ return vsperf_pb2.StatusReply(message="Test File is not uploaded yet [!] " \
+ "\nUpload Test Configuration File.")
+ self.remove_uploaded_config()
+ return vsperf_pb2.StatusReply(
+ message="Successfully All Uploaded Config Files Removed")
+
+ def RemoveCollectd(self, request, context):
+ """
+ Handle collectd removal command from client
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ self.collectd_remove()
+ return vsperf_pb2.StatusReply(
+ message="Successfully Collectd Removed From DUT-Host")
+
+ def RemoveEverything(self, request, context):
+ """
+ Handle of removing everything from DUT command from client
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ if self.tgen_check != 0:
+ return vsperf_pb2.StatusReply(message="TGen-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " TGen-Host.")
+ self.vsperf_remove()
+ self.result_folder_remove()
+ self.remove_uploaded_config()
+ self.collectd_remove()
+ return vsperf_pb2.StatusReply(
+ message="Successfully Everything Removed From DUT-Host")
+
+ def StartTGen(self, request, context):
+ """
+ Handle start-Tgen command from client
+ """
+ if self.tgen_check != 0:
+ return vsperf_pb2.StatusReply(message="TGen-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " TGen-Host.")
+ self.trex_params = request.params
+ run_cmd = "cd trex_2.37/scripts ; "
+ run_cmd += "./t-rex-64 "
+ run_cmd += self.trex_params
+ self.tgen_client.send_command(run_cmd)
+ self.tgen_start_check = 1
+ return vsperf_pb2.StatusReply(message="T-Rex Successfully running...")
+
+ def SanityCollectdCheck(self, request, context):
+ """
+ Check and verify collectd is able to run and start properly
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ check_collectd_cmd = "find /opt -maxdepth 1 -name 'collectd'"
+ check_test_result = str(self.client.execute(check_collectd_cmd)[1])
+ if "collectd" in check_test_result:
+ check_collectd_run_cmd = "echo {} | sudo -S service collectd start".format(self.pwd)
+ self.client.run(check_collectd_run_cmd, pty=True)
+ check_collectd_status_cmd = "ps aux | grep collectd"
+ check_collectd_status = str(self.client.execute(check_collectd_status_cmd)[1])
+ if "/sbin/collectd" in check_collectd_status:
+ self.sanity_check_done_list.append(int(5))
+ return vsperf_pb2.StatusReply(message="Collectd is working Fine")
+ return vsperf_pb2.StatusReply(message="Collectd Fail to Start, \
+ Install correctly before running Test")
+ return vsperf_pb2.StatusReply(message="Collectd is not installed yet.")
+
+ def SanityVNFpath(self, request, context):
+ """
+ Check if VNF image available on the mention path in Test Config File
+ """
+ # fetch the VNF path we placed in vsperf.conf file
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ if self.test_upload_check == 0:
+ return vsperf_pb2.StatusReply(message="Test File is not uploaded yet [!] " \
+ "\nUpload Test Configuration File.")
+ vsperf_conf_path = 'cat ~/{} | grep "GUEST_IMAGE"'.format(self.conffile)
+ vsperf_conf_read = self.client.execute(vsperf_conf_path)[1]
+ vnf_image_path = vsperf_conf_read.split("'")[1]
+ vnf_path_check_cmd = "find {}".format(vnf_image_path)
+ vfn_path_check_result = str(self.client.execute(vnf_path_check_cmd)[1])
+ if vnf_image_path in vfn_path_check_result:
+ self.sanity_check_done_list.append(int(2))
+ return vsperf_pb2.StatusReply(message="Test Configratuion file has Correct "\
+ "VNF path information on DUT-Host.....[OK]")
+ return vsperf_pb2.StatusReply(message='Test Configuration file has wrongly placed VNF '\
+ 'path information \n'\
+ 'VNF is not available on DUT-Host................................[Failed]\n ')
+
+ def SanityVSPERFCheck(self, request, context):
+ """
+ We have to make sure that VSPERF install correctly
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ vsperf_check_command = "source ~/vsperfenv/bin/activate ; cd vswitchperf* && "
+ vsperf_check_command += "./vsperf --help"
+ vsperf_check_cmd_result = str(self.client.execute(vsperf_check_command)[1])
+ vsperf_verify_list = [
+ 'usage',
+ 'positional arguments',
+ 'optional arguments',
+ 'test selection options',
+ 'test behavior options']
+ for idx, i in enumerate(vsperf_verify_list, start=1):
+ if str(i) in vsperf_check_cmd_result:
+ if idx < 5:
+ continue
+ elif idx == 5:
+ self.sanity_check_done_list.append(int(1))
+ return vsperf_pb2.StatusReply(
+ message="VSPERF Installed Correctly and Working fine")
+ return vsperf_pb2.StatusReply(message="VSPERF Does Not Installed Correctly ," \
+ "INSTALL IT AGAIN..............[Critical]")
+ return vsperf_pb2.StatusReply(message="VSPERF Does Not Installed Correctly ," \
+ "INSTALL IT AGAIN..............[Critical]")
+
+ def SanityNICCheck(self, request, context):
+ """
+ Check either NIC PCI ids are Correctly placed or not
+ """
+ if self.tgen_check != 0:
+ return vsperf_pb2.StatusReply(message="TGen-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " TGen-Host.")
+ trex_conf_path = "cat /etc/trex_cfg.yaml | grep interfaces"
+ trex_conf_read = self.tgen_client.execute(trex_conf_path)[1]
+ nic_pid_ids_list = [trex_conf_read.split("\"")[1], trex_conf_read.split("\"")[3]]
+ trex_nic_pic_id_cmd = "lspci | egrep -i --color 'network|ethernet'"
+ trex_nic_pic_id = str(self.tgen_client.execute(trex_nic_pic_id_cmd)[1]).split('\n')
+ acheck = 0
+ for k in trex_nic_pic_id:
+ for j in nic_pid_ids_list:
+ if j in k:
+ acheck += 1
+ else:
+ pass
+ if acheck == 2:
+ self.sanity_check_done_list.append(int(3))
+ return vsperf_pb2.StatusReply(message="Both the NIC PCI Ids are Correctly "\
+ "configured on TGen-Host..............")
+ return vsperf_pb2.StatusReply(message="You configured NIC PCI Ids Wrong in "\
+ "TGen-Host............................[OK]\n")
+
+ def SanityTgenConnDUTCheck(self, request, context):
+ """
+ We should confirm the DUT connectivity with the Tgen and Traffic Generator is working or not
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ self.tgen_ip_address = request.ip
+ tgen_connectivity_check_cmd = "ping {} -c 1".format(
+ self.tgen_ip_address)
+ tgen_connectivity_check_result = int(
+ self.client.execute(tgen_connectivity_check_cmd)[0])
+ if tgen_connectivity_check_result == 0:
+ self.sanity_check_done_list.append(int(6))
+ return vsperf_pb2.StatusReply(
+ message="DUT-Host is successfully reachable to Traffic Generator......")
+ return vsperf_pb2.StatusReply(message="DUT-Host is unsuccessful to reach the \
+ Traffic Generator \nMake sure to establish connection \
+ between DUT-Host and TGen-Host before running Test\
+ ............... ")
+
+ def variable_from_test_config(self, aparameter):
+ """This function can be use to read any configuration paramter from vsperf.conf"""
+ read_cmd = 'cat ~/{} | grep "{}"'.format(aparameter, self.conffile)
+ read_cmd_output = str(self.client.execute(read_cmd)[1])
+ print(read_cmd_output)
+ if not read_cmd_output or '#' in read_cmd_output:
+ return 0
+ return read_cmd_output.split("=")[1].strip()
+
+ def cpumask2coreids(self, mask):
+ """conver mask to coreids"""
+ intmask = int(mask, 16)
+ i = 1
+ coreids = []
+ while i < intmask:
+ if i & intmask:
+ coreids.append(str(math.frexp(i)[1]-1))
+ i = i << 1
+ return coreids
+
+ def cpu_allocation_check(self, list1, list2):
+ """compare to cpu_map list"""
+ if len(list1) >= len(list2):
+ if all(elem in list1 for elem in list2):
+ self.sanity_check_done_list.append(int(4))
+ return vsperf_pb2.StatusReply(message="CPU allocation properly done on" \
+ " DUT-Host.................[OK]")
+ return vsperf_pb2.StatusReply(message="CPU allocation not done properly on " \
+ "DUT-Host............[Failed]")
+ return vsperf_pb2.StatusReply(message="CPU allocation not done properly on" \
+ " DUT-Host............[Failed]")
+
+ def SanityCPUAllocationCheck(self, request, context):
+ """
+ check for cpu-allocation on DUT-Host
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ if self.test_upload_check == 0:
+ return vsperf_pb2.StatusReply(message="Test File is not uploaded yet [!] " \
+ "\nUpload Test Configuration File.")
+ read_setting_cmd = "source vsperfenv/bin/activate ; cd vswitchperf* && "
+ read_setting_cmd += './vsperf --list-settings'
+ default_vsperf_settings = ast.literal_eval(str(self.client.execute(read_setting_cmd)[1]))
+ default_cpu_map = default_vsperf_settings["VSWITCH_VHOST_CPU_MAP"]
+ default_vswitch_pmd_cpu_mask = str(default_vsperf_settings["VSWITCH_PMD_CPU_MASK"])
+ default_vswitch_vhost_cpu_map = [str(x) for x in default_cpu_map]
+ vswitch_pmd_cpu_mask = self.variable_from_test_config("VSWITCH_PMD_CPU_MASK")
+ vswitch_cpu_map = (self.variable_from_test_config("VSWITCH_VHOST_CPU_MAP"))
+ vswitch_vhost_cpu_map = 0
+
+ if vswitch_cpu_map != 0:
+ vswitch_vhost_cpu_map = [str(x) for x in ast.literal_eval(vswitch_cpu_map)]
+
+ if vswitch_pmd_cpu_mask == 0 and vswitch_vhost_cpu_map == 0:
+ self.sanity_check_done_list.append(int(4))
+ return vsperf_pb2.StatusReply(message="CPU allocation Check Done,"\
+ "\nNo vswitch_pmd_cpu_mask or vswitch_vhost_cpu_map assign in test " \
+ "configuration file.\nUsing Default Settings..[OK]\n")
+ if vswitch_pmd_cpu_mask != 0 and vswitch_vhost_cpu_map == 0:
+ core_id = self.cpumask2coreids(vswitch_pmd_cpu_mask)
+ return self.cpu_allocation_check(default_vswitch_vhost_cpu_map, core_id)
+ if vswitch_pmd_cpu_mask == 0 and vswitch_vhost_cpu_map != 0:
+ core_id_1 = self.cpumask2coreids(default_vswitch_pmd_cpu_mask)
+ return self.cpu_allocation_check(vswitch_vhost_cpu_map, core_id_1)
+ core_id_2 = self.cpumask2coreids(vswitch_pmd_cpu_mask)
+ return self.cpu_allocation_check(vswitch_vhost_cpu_map, core_id_2)
+
+ def GetVSPERFConffromDUT(self, request, context):
+ """
+ This will extract the vsperf test configuration from DUT-Host
+ """
+ if self.dut_check != 0:
+ return vsperf_pb2.StatusReply(message="DUT-Host is not Connected [!]" \
+ "\nMake sure to establish connection with" \
+ " DUT-Host.")
+ if self.test_upload_check == 0:
+ return vsperf_pb2.StatusReply(message="Test File is not uploaded yet [!] " \
+ "\nUpload Test Configuration File.")
+ read_cmd = "cat ~/{}".format(self.conffile)
+ read_cmd_output = str(self.client.execute(read_cmd)[1])
+ return vsperf_pb2.StatusReply(message="{}".format(read_cmd_output))
+
+
+def serve():
+ """
+ Start servicing the client
+ """
+ server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
+ vsperf_pb2_grpc.add_ControllerServicer_to_server(
+ VsperfController(), server)
+ server.add_insecure_port('[::]:50052')
+ server.start()
+ try:
+ while True:
+ time.sleep(_ONE_DAY_IN_SECONDS)
+ except (SystemExit, KeyboardInterrupt, MemoryError, RuntimeError):
+ server.stop(0)
+
+
+if __name__ == "__main__":
+ serve()
diff --git a/tools/docker/testcontrol/interactive/docker-compose.yml b/tools/docker/testcontrol/interactive/docker-compose.yml
new file mode 100644
index 00000000..431de124
--- /dev/null
+++ b/tools/docker/testcontrol/interactive/docker-compose.yml
@@ -0,0 +1,20 @@
+version: '2'
+
+services:
+ testcontrol:
+ build:
+ context: ./controller
+ volumes:
+ - ./controller/vsperf:/vsperf
+ ports:
+ - 50052:50052
+
+
+
+
+
+
+
+
+
+