Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions ansible/library/exabgp.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os
import re
import time
import six

DOCUMENTATION = '''
module: exabgp
Expand Down Expand Up @@ -48,6 +49,7 @@
http_api_py = '''\
from flask import Flask, request
import sys
import six
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated import six here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import here is part of http_api_py which is the HTTP API processor generated as /usr/share/exabgp/http_api.py on the PTF container and is not for the exabgp.py.


#Disable banner msg from app.run, or the output might be caught by exabgp and run as command
cli = sys.modules['flask.cli']
Expand All @@ -58,7 +60,15 @@
# Setup a command route to listen for prefix advertisements
@app.route('/', methods=['POST'])
def run_command():
if request.form.has_key('commands'):
# code made compatible to run in Py2 or Py3 environment
# to support back-porting
request_has_commands = False
if six.PY2:
request_has_commands = request.form.has_key('commands')
else:
request_has_commands = 'commands' in request.form

if request_has_commands:
cmds = request.form['commands'].split(';')
else:
cmds = [ request.form['command'] ]
Expand All @@ -82,7 +92,8 @@ def run_command():
}
'''

exabgp_conf_tmpl = '''\
# ExaBGP Version 3 configuration file format
exabgp3_config_template = '''\
group exabgp {
{{ dump_config }}

Expand All @@ -105,6 +116,35 @@ def run_command():
}
'''

# ExaBGP Version 4 uses a different configuration file
# format. The dump_config would come from the user. The caller
# must pass Version 4 compatible configuration.
# Example configs are available here
# https://github.com/Exa-Networks/exabgp/tree/master/etc/exabgp
# Look for sample for a given section for details
exabgp4_config_template = '''\
{{ dump_config }}
process http-api {
run /usr/bin/python /usr/share/exabgp/http_api.py {{ port }};
encoder json;
}
neighbor {{ peer_ip }} {
router-id {{ router_id }};
local-address {{ local_ip }};
peer-as {{ peer_asn }};
local-as {{ local_asn }};
auto-flush {{ auto_flush }};
group-updates {{ group_updates }};
{%- if passive %}
passive;
listen {{ listen_port }};
{%- endif %}
api {
processes [ http-api ];
}
}
'''

exabgp_supervisord_conf_tmpl = '''\
[program:exabgp-{{ name }}]
command=/usr/local/bin/exabgp /etc/exabgp/{{ name }}.conf
Expand Down Expand Up @@ -182,7 +222,12 @@ def setup_exabgp_conf(name, router_id, local_ip, peer_ip, local_asn, peer_asn, p
dump_config = jinja2.Template(
dump_config_tmpl).render(dump_script=dump_script)

t = jinja2.Template(exabgp_conf_tmpl)
# backport friendly checking; not required if everything is Py3
t = None
if six.PY2:
t = jinja2.Template(exabgp3_config_template)
else:
t = jinja2.Template(exabgp4_config_template)
data = t.render(name=name,
router_id=router_id,
local_ip=local_ip,
Expand Down
103 changes: 90 additions & 13 deletions tests/ptf_runner.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import ast
import pathlib
import pipes
import traceback
import logging
import allure
import json
from datetime import datetime
import os
import six

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -46,29 +50,101 @@ def get_dut_type(host):
return "Unknown"


def get_ptf_image_type(host):
"""
The function queries the PTF image to determine
if the image is of type 'mixed' or 'py3only'
"""
pyvenv = host.stat(path="/root/env-python3/pyvenv.cfg")
if pyvenv["stat"]["exists"]:
return "mixed"
return "py3only"


def get_test_path(testdir, testname):
"""
Returns two values
- first: the complete path of the test based on testdir and testname.
- second: True if file is in 'py3' False otherwise
Raises FileNotFoundError if file is not found
"""
curr_path = os.path.dirname(os.path.abspath(__file__))
base_path = pathlib.Path(curr_path).joinpath('..').joinpath('ansible/roles/test/files').joinpath(testdir)
idx = testname.find('.')
test_fname = testname + '.py' if idx == -1 else testname[:idx] + '.py'
chk_path = base_path.joinpath('py3').joinpath(test_fname)
if chk_path.exists():
return chk_path, True
chk_path = base_path.joinpath(test_fname)
if chk_path.exists():
return chk_path, False
raise FileNotFoundError("Testdir: {} Testname: {} File: {} not found".format(testdir, testname, chk_path))


def is_py3_compat(test_fpath):
"""
Returns True if the test can be run in a Python 3 environment
False otherwise.
"""
if six.PY2:
raise Exception("must run in a Python 3 runtime")
with open(test_fpath, 'rb') as f:
code = f.read()
try:
ast.parse(code)
except SyntaxError:
return False
return True
# shouldn't get here
return False


def ptf_runner(host, testdir, testname, platform_dir=None, params={},
platform="remote", qlen=0, relax=True, debug_level="info",
socket_recv_size=None, log_file=None, device_sockets=[], timeout=0, custom_options="",
module_ignore_errors=False, is_python3=False, async_mode=False, pdb=False):
# Call virtual env ptf for migrated py3 scripts.
# ptf will load all scripts under ptftests, it will throw error for py2 scripts.
# So move migrated scripts to seperated py3 folder avoid impacting py2 scripts.
module_ignore_errors=False, is_python3=None, async_mode=False, pdb=False):

dut_type = get_dut_type(host)
if dut_type == "kvm" and params.get("kvm_support", True) is False:
logger.info("Skip test case {} for not support on KVM DUT".format(testname))
return True

if is_python3:
path_exists = host.stat(path="/root/env-python3/bin/ptf")
if path_exists["stat"]["exists"]:
cmd = "/root/env-python3/bin/ptf --test-dir {} {}".format(testdir + '/py3', testname)
cmd = ""
ptf_img_type = get_ptf_image_type(host)
logger.info('PTF image type: {}'.format(ptf_img_type))
test_fpath, in_py3 = get_test_path(testdir, testname)
logger.info('Test file path {}, in py3: {}'.format(test_fpath, in_py3))
is_python3 = is_py3_compat(test_fpath)

# The logic below automatically chooses the PTF binary to execute a test script
# based on the container type "mixed" vs. "py3only".
#
# For "mixed" type PTF image the global environment has Python 2 and Python 2 compatible
# ptf binary. Python 3 is part of a virtual environment under "/root/env-python3". All
# packages and Python 3 compatible ptf binary is in the virtual environment.
#
# For "py3only" type PTF image the global environment has Python 3 only in the global
# environment. Python 2 does not exist on this image and attempt to execute any
# Python 2 PTF tests raises an exception.

ptf_cmd = None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably add some comments here to better explain the logic here.
The expectation of PTF image is either "mixed" or "py3only". "mixed" means that global python is python2. Python3 and dependent package is installed in /root/env-python3. "py3only" means that there is no python2. The global python is python3 and it has all required dependent packages.
For "mixed" ptf image, use python3 version ptf command to run python3 compatible ptf scripts. Use python2 version ptf command to run legacy python2 PTF scripts.
For "py3only" ptf image, always use the global python3 ptf command to run PTF scripts. Raise exception if ptf script is not python3 compatible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added the comment.

if ptf_img_type == "mixed":
if is_python3:
ptf_cmd = '/root/env-python3/bin/ptf'
else:
error_msg = "Virtual environment for Python3 /root/env-python3/bin/ptf doesn't exist.\n" \
"Please check and update docker-ptf image, make sure to use the correct one."
logger.error("Exception caught while executing case: {}. Error message: {}".format(testname, error_msg))
raise Exception(error_msg)
ptf_cmd = '/usr/bin/ptf'
else:
if is_python3:
ptf_cmd = '/usr/local/bin/ptf'
else:
err_msg = 'cannot run Python 2 test in a Python 3 only {} {}'.format(testdir, testname)
raise Exception(err_msg)

if in_py3:
tdir = pathlib.Path(testdir).joinpath('py3')
cmd = "{} --test-dir {} {}".format(ptf_cmd, tdir, testname)
else:
cmd = "ptf --test-dir {} {}".format(testdir, testname)
cmd = "{} --test-dir {} {}".format(ptf_cmd, testdir, testname)

if platform_dir:
cmd += " --platform-dir {}".format(platform_dir)
Expand Down Expand Up @@ -120,6 +196,7 @@ def ptf_runner(host, testdir, testname, platform_dir=None, params={},
print("Run command from ptf: sh {}".format(script_name))
import pdb
pdb.set_trace()
logger.info('ptf command: {}'.format(cmd))
result = host.shell(cmd, chdir="/root", module_ignore_errors=module_ignore_errors, module_async=async_mode)
if not async_mode:
if log_file:
Expand Down
1 change: 0 additions & 1 deletion tests/templates/icmp_responder.conf.j2
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
[program:icmp_responder]
command=/root/env-python3/bin/python /opt/icmp_responder.py {{ icmp_responder_args }}
process_name=icmp_responder
environment=PATH="/root/env-python3/bin"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove this line and keep /root/env-python3/bin/python in line 2? If the purpose is to use whatever default python binary, then line 2 should be updated as well. Eventually /root/env-python3 may be removed in "py3only" ptf image.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The environment is not required for the script to run. To keep backporting simpler I have done away from modifying the path to invoke Python 3. This PR enables that sonic-net/sonic-buildimage#19813. Can you please review this too.

stdout_logfile=/tmp/icmp_responder.out.log
stderr_logfile=/tmp/icmp_responder.err.log
redirect_stderr=false
Expand Down