Skip to content

Commit

Permalink
Merge pull request #44 from avinetworks/rc-21.1.3
Browse files Browse the repository at this point in the history
updating with 21.1.3 assets
  • Loading branch information
Chaitanya Deshpande authored Dec 21, 2021
2 parents 79fcb58 + 50f1217 commit cce7e4e
Show file tree
Hide file tree
Showing 19 changed files with 567 additions and 66 deletions.
6 changes: 4 additions & 2 deletions defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ se_mounts_default:
- "/etc/localtime:/etc/localtime"
- "{{ (se_disk_path == None) | ternary('', se_disk_path ~ ':/vol/') }}"
- "{{ (se_logs_disk_path == None) | ternary('', se_logs_disk_path ~ ':/vol_logs/') }}"
- "{{ (se_deploy_type == 'podman') | ternary('/run/podman/io.podman:/var/run/docker.sock', '/var/run/docker.sock:/var/run/docker.sock') }}"
- "{{ (se_deploy_type == 'podman') | ternary('/run/podman/podman.sock:/var/run/docker.sock', '/var/run/docker.sock:/var/run/docker.sock') }}"
se_mounts_all: "{{ se_mounts_extras + se_mounts_default}}"
se_mounts_string: "{% for mount_var in se_mounts_all|reject('match', '^$') %} -v {{ mount_var }}{% endfor %}"

Expand All @@ -93,4 +93,6 @@ se_env_variables_string: "{% for env_var in se_env_variables_all|reject('match',
cpu_constraint: ""

# !!!! BEWARE: This is to completely override everything passed into the service template for the docker run. DON'T EDIT THIS UNLESS YOU KNOW WHAT YOUR DOING!!!!!
se_docker_run_params: "--name=avise -m {{ se_memory_mb|int }}m {{ cpu_constraint }} -d{{ se_env_variables_string }} --net=host{{ se_mounts_string }} --privileged=true {{ se_image }}"
AVI_RUN_PARAMS: "--name=avise --pids-limit -1 -v /run/xtables.lock:/run/xtables.lock -m {{ se_memory_mb|int }}m {{ cpu_constraint }} -d{{ se_env_variables_string }} --net=host{{ se_mounts_string }} --privileged=true {{ se_image }}"
AVI_EXECUTABLE: "/usr/sbin/avise"
se_service_files_path: ""
193 changes: 193 additions & 0 deletions files/systemd/avi_host_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
############################################################################
# ========================================================================
# Copyright 2021 VMware, Inc. All rights reserved. VMware Confidential
# ========================================================================
###

#**************************************************************************
# ------------------------------------------------------------------------
# Copyright 2021 VMware, Inc. All rights reserved. VMware Confidential
# ------------------------------------------------------------------------
#*

"""
What is the service functionality?
> To accept a command as string on Unix Domain Socket and respond with out, err and return code
How is the service started?
> The service is started by Cloud Connector before SE container comes up in LSC.
How is the service stopped?
> Stopping avise service automatically stop avihost service in its post stop section
Where does it run?
> The service runs on the host of SE in LSC mode. NOTE: The service is applicable only for container based deployments
What happens when exceptions are hit?
> The service is run with auto-restart by systemd and it is stateless.
What is the location of its log-files on the host.
> Logs are stored at `/var/log/avi_host.log` of the host with logrotation enabled
Are there any service dependencies with respect to SE container?
> It needs python2 / python3 to be installed in the host
If the SE container is stopped, will avihost service get stopped?
> Yes, it is done as a part of poststop section of avise
Can the service run when the container is not running?
> We will stop the service upon SE service termination.
The service running post SE stopped causes no harm, though.
Any upgrade implications? when a SE container is upgraded from x to x+1, will the host service also get upgraded from x to x+1 ?
> The avihost is written and maintained in such a fashion that it is lightweight and can support different versions of AVI SE.
Having said that, we upgrade avihost and related items, whenever SE is getting upgraded.
"""

import socket
import sys
import os
import subprocess
import signal
import logging
import traceback
import re
from logging.handlers import RotatingFileHandler

log_file="/var/log/avi_host.log"
logger=logging.getLogger()
logger.setLevel(logging.DEBUG)
#20MB file limit for logging
handler = RotatingFileHandler(log_file, maxBytes=20*1024*1024, backupCount=1)
formatter = logging.Formatter("%(asctime)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)

SERVER_ADDRESS = '/root/se_domain_socket'
CURRENT_VERSION = "v1"

def send_response(conn, version, returncode, output, error):
# Send the response back to the client

output, error = output.strip(), error.strip()

if version == CURRENT_VERSION:
conn.sendall("#version:{}#ret:{}#aviout:".format(CURRENT_VERSION, returncode).encode("utf-8"))
conn.sendall(output)
conn.sendall("#avierror:".encode("utf-8"))
conn.sendall(error)
conn.sendall("#avidone#".encode("utf-8"))
logger.debug("Sent response for {}".format(CURRENT_VERSION))
return

# Legacy handling of o/p and error differently
if returncode:
logger.error("command execution failed {} {}".format(error, returncode))
conn.sendall(error)
conn.sendall("#ret:{}#avierror#".format(returncode).encode("utf-8"))
else:
logger.debug("command output {}".format(output))
conn.sendall(output)
conn.sendall("#avidone#".encode("utf-8"))
logger.debug("Sent response for Legacy version")

def block_and_recv(conn):
# Receive the data in small chunks and retransmit it
input_str = ""
while True:
data = conn.recv(64)
input_str = input_str + data.decode("utf-8")
logger.debug("input_str={}".format(input_str))
if "#avicmddone#" in input_str:
split_list = re.split("#version:|#cmd:|#avicmddone#", input_str)
if len(split_list) == 2:
# Legacy format: "<command>#avicmddone#"
# split_list = ["<command>", ""]
return [split_list[1], split_list[0]]
else:
# v1 format: "#version:{version_tag}#cmd:{command}#avicmddone#"
# split_list = ["", "<command>", "<version_tag>", ""]
return split_list[1:-1]

def create_uds_socket():
# Make sure the socket does not already exist
try:
os.unlink(SERVER_ADDRESS)
except OSError as error:
if os.path.exists(SERVER_ADDRESS):
exception = traceback.format_exc()
logger.error("Failed to unlink domain stream socket: {}".format(exception))
raise Exception("Failed to unlink domain stream socket: {}".format(exception))

# Create a UDS socket
try:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.bind(SERVER_ADDRESS)
# Listen for incoming connections
sock.listen(1)
logger.info("Listening on %s", SERVER_ADDRESS)
return sock
except:
exception = traceback.format_exc()
logger.error("socket listen failed: {}".format(exception))
raise Exception("socket listen failed: {}".format(exception))

def run():
sock = create_uds_socket()
while True:
# Valid Formats:
# Legacy: (20.1.4 - <21.1.1)
# Input : {command}#avicmddone#
# Output :
# * If error : {error}#ret:{return_code}#avierror#
# * Else : {output}#avidone#
#
# v1: (21.1.1 and above)
# Input : #version:{version_tag}#{command}#avicmddone#
# Output : #version:{version_tag}#ret:{return_code}#aviout:{output}#avierror:{error}#avidone#

# Wait for a connection
logger.debug('waiting for a connection')
conn, client_address = sock.accept()
version = CURRENT_VERSION
try:
version, command = block_and_recv(conn)
version, command = version.strip(), command.strip()

# Empty command, send error response
if not command:
send_response(conn, version, 255, b"", b"Received empty command")
continue

# AVI induced crash (A way to restart the service)
if command == "#avicrash#":
raise Exception("AVI induced crash")

# If process needs to be run in background
if command.endswith("&"):
returncode = os.system(command)
send_response(conn, version, returncode, b"", b"")
continue

# Execute command with timeout of 120 seconds and send response for other commands
logger.debug("received command {}".format(command))
command = "timeout 120 " + command
op = subprocess.Popen(command, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
output, error = op.communicate()
returncode = op.returncode
send_response(conn, version, returncode, output, error)

# If exception is hit, log the exception, send it to the client,
# raise another exception and let the process crash
except Exception as ex:
exception = traceback.format_exc()
logger.error("Exception hit: {}".format(exception))
send_response(conn, version, 255, b"", exception.encode("utf-8"))
raise Exception("Exception hit: {}".format(exception))

# Clean up the connection
finally:
conn.close()

if __name__ == "__main__":
run()
17 changes: 15 additions & 2 deletions files/systemd/avihost.service
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
#***************************************************************************
# ------------------------------------------------------------------------
# Copyright 2021 VMware, Inc. All rights reserved. VMware Confidential
# ------------------------------------------------------------------------
#

[Unit]
Description=Avi Host Service

[Service]
TimeoutStartSec=0
Restart=always

ExecStart=/bin/sh -c "python3 /usr/sbin/avi_host_server.py"
ExecStop=/bin/sh -c "pkill -f avi_host_server.py"
ExecStart=/bin/bash /etc/systemd/system/avihost_service_script.sh start
ExecStop=/bin/bash /etc/systemd/system/avihost_service_script.sh stop
# When we run ifup command via avihost service,
# the dhclient if spawned though being a child of PID 1
# is under avihost cgroup.
# In case of avihost service crash / restart the dhclient process must not be killed.
# Making the KillMode=process there by no process other than the main python process
# which is stateless is respawned.
KillMode=process

[Install]
WantedBy=multi-user.target
52 changes: 52 additions & 0 deletions files/systemd/avihost_service_script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/bash -x

############################################################################
# ========================================================================
# Copyright 2021 VMware, Inc. All rights reserved. VMware Confidential
# ========================================================================
###

#***************************************************************************
# ------------------------------------------------------------------------
# Copyright 2021 VMware, Inc. All rights reserved. VMware Confidential
# ------------------------------------------------------------------------
#

start()
{
if [[ -f /opt/avitest_python_version ]]; then
# For testing against python2 and python3
version=$(cat /opt/avitest_python_version)
else
# Ref: https://stackoverflow.com/a/38485534/9328077
version='0'
command -v python >/dev/null 2>&1 && version=''
command -v python2 >/dev/null 2>&1 && version='2'
command -v python3 >/dev/null 2>&1 && version='3'
fi

if [[ $version != '0' ]]; then
exec python$version /usr/sbin/avi_host_server.py
else
echo "Unable to find any installed python"
exit 1
fi
}

stop()
{
pkill -f avi_host_server.py
}

case "$1" in
"start")
start
;;
"stop")
stop
;;
*)
echo "unhandled case for avihost service"
exit 1
;;
esac
Loading

0 comments on commit cce7e4e

Please sign in to comment.