diff options
author | Sridhar K. N. Rao <sridhar.rao@spirent.com> | 2019-10-02 17:50:23 +0530 |
---|---|---|
committer | Sridhar K. N. Rao <sridhar.rao@spirent.com> | 2019-12-01 09:02:14 +0530 |
commit | 601b88e2c5dabaa7fe2035c7e433d2da5b860c4b (patch) | |
tree | 45d7336a3fc71907c19720127d154f8c44aa673a /tools/docker/deployment/interactive | |
parent | b984a2f40bec349c802b631a69526590b34bd5de (diff) |
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 <sridhar.rao@spirent.com>
Change-Id: Id40b02960f71a7f9183d9a53955e2483117fb9e2
Diffstat (limited to 'tools/docker/deployment/interactive')
4 files changed, 403 insertions, 0 deletions
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 + + + + + + + + + + + |