From 601b88e2c5dabaa7fe2035c7e433d2da5b860c4b Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Wed, 2 Oct 2019 17:50:23 +0530 Subject: Tools: Deployment and TestControl Containers This patch add containerization of VSPERF support. The patch facilitates creation of 4 containers: 1. Interactive Deployment 2. Auto Deployment 3. Interactive TestControl 4. Auto TestControl. The patch also includes a minimal client to work with interactive containers. The docs folder provides detailed documentation. Fixed pylint errors in libs folder. Removed proto built python files, and added the build process in prepare.sh. Stability improvements for Auto versions of deployment and testcontrol. Enhance client with 'mode' feature, where client can run either to do only deploy/only test or both. Add sample configuration file for client Fixed few typos - as suggested by AL. JIRA: VSPERF-594 Signed-off-by: Sridhar K. N. Rao Change-Id: Id40b02960f71a7f9183d9a53955e2483117fb9e2 --- .../deployment/interactive/controller/Dockerfile | 21 ++ .../interactive/controller/vsperf/__init__.py | 1 + .../controller/vsperf/vsperf_controller.py | 360 +++++++++++++++++++++ 3 files changed, 382 insertions(+) create mode 100644 tools/docker/deployment/interactive/controller/Dockerfile create mode 100644 tools/docker/deployment/interactive/controller/vsperf/__init__.py create mode 100644 tools/docker/deployment/interactive/controller/vsperf/vsperf_controller.py (limited to 'tools/docker/deployment/interactive/controller') 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() -- cgit 1.2.3-korg