Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions ansible/config_sonic_basedon_testbed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@
delegate_to: localhost
when: "'dualtor' in topo"

- name: generate y_cable simulator driver
include_tasks: dualtor/config_y_cable_simulator.yml
when: "'dualtor' in topo"

- name: set default vm file path
set_fact:
vm_file: veos
Expand Down
30 changes: 30 additions & 0 deletions ansible/dualtor/config_y_cable_simulator.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
- fail: msg="There must be two duts in this testbed"
when: "testbed_facts['duts']|length != 2"

- fail: msg="The type of testbed must be dualtor"
when: "'dualtor' not in testbed_facts['topo']"

- fail: msg="The DUT you are trying to run test does not belongs to this testbed"
when: inventory_hostname not in testbed_facts['duts']

- name: get host server address
vmhost_server_info: vmhost_server_name={{ testbed_facts['server'] }} vm_file='veos'
delegate_to: localhost

- name: set y cable simulator server address
set_fact:
mux_simulator_server: "{{ vmhost_server_address }}"

- name: set default y cable simulator server port
set_fact:
mux_simulator_port: 8080
when: mux_simulator_port is not defined

- name: generate y cable simulator driver for {{ dut_name }}
include_tasks: "dualtor/y_cable_simulator_injector.yml"
loop: "{{ testbed_facts['duts'] }}"
loop_control:
loop_var: dut_name
run_once: True

156 changes: 156 additions & 0 deletions ansible/dualtor/y_cable_simulator_client.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
from urllib import request, error
import json
from sonic_py_common import logger

DUTS_MAP = {
"{{ dual_tor_facts['positions']['upper'] }}": 0,
"{{ dual_tor_facts['positions']['lower'] }}": 1
}

VM_SET = "{{ testbed_facts['group-name'] }}"

DUT_NAME = "{{ dut_name }}"

BASE_URL = "http://{{ mux_simulator_server }}:{{ mux_simulator_port }}/"

SYSLOG_IDENTIFIER = "y_cable_sim"
helper_logger = logger.Logger(SYSLOG_IDENTIFIER)

TOR_A = "tor_a"
TOR_B = "tor_b"

def _url(physical_port):
"""
Helper function to build an url for given physical_port

Args:
physical_port: physical port on switch, an integer starting from 1
Returns:
str: The url for post/get.
"""
return BASE_URL + "/mux/{}/{}".format(VM_SET, physical_port - 1)

def _post(physical_port, data):
"""
Helper function for posting data to y_cable server.

Args:
physical_port: physical port on switch, an integer starting from 1
data: data to post
Returns:
True if succeed. False otherwise
"""
data = json.dumps(data).encode(encoding='utf-8')
header = {'Accept': 'application/json', 'Content-Type': 'application/json'}
req = request.Request(url=_url(physical_port), data=data, headers=header)
try:
_ = request.urlopen(req)
except error.HTTPError as e:
try:
err_msg = json.loads(e.read().decode())['err_msg']
helper_logger.log_warning("post request returns err. status_code = {} err_msg = {}".format(e.code, err_msg))
except Exception:
helper_logger.log_warning("post request returns err. status_code = {}".format(e.code))
return False
except error.URLError as e:
helper_logger.log_warning("post request returns err. err_msg = {}".format(str(e)))
return False
return True

def _get(physical_port):
"""
Helper function for polling status from y_cable server.

Args:
physical_port: physical port on switch, an integer starting from 1
Returns:
dict: A dict decoded from server's response.
None: Returns None is error is detected.
"""
req = request.Request(url=_url(physical_port))
try:
res = request.urlopen(req)
data = res.read()
return json.loads(data)
except error.HTTPError as e:
err_msg = json.loads(e.read().decode())['err_msg']
helper_logger.log_warning("get request returns err. status_code = {} err_msg = {}".format(e.code, err_msg))
except error.URLError as e:
helper_logger.log_warning("get request returns err. err_msg = {}".format(str(e)))
except json.decoder.JSONDecodeError as e:
helper_logger.log_warning("failed to parse response as json. err_msg = {}".format(str(e)))
except Exception as e:
helper_logger.log_warning("get request returns err. err_msg = {}".format(str(e)))
return None

def _toggle_to(physical_port, target):
"""
Helper function for toggling to certain TOR.

Args:
physical_port: physical port on switch, an integer starting from 1
target: TOR_A / TOR_B
Returns:
True if succeed. False otherwise
"""
data = {"active_side": target}
helper_logger.log_info("physical_port {} toggle to {}".format(physical_port, target))
return _post(physical_port, data)

def _get_side(physical_port):
"""
Retrieve the current active tor from y_cable simulator server.
Args:
physical_port: physical port on switch, an integer starting from 1
Returns:
1 if TOR_A is active
2 if TOR_B is active
-1 for exception or inconstient status
"""
res = _get(physical_port)
if not res:
return -1
active_side = res["active_side"]
if active_side == TOR_A:
return 1
elif active_side == TOR_B:
return 2
else:
return -1

def toggle_mux_to_torA(physical_port):
return _toggle_to(physical_port, TOR_A)

def toggle_mux_to_torB(physical_port):
return _toggle_to(physical_port, TOR_B)

def check_read_side(physical_port):
return DUTS_MAP[DUT_NAME] + 1

def check_mux_direction(physical_port):
return _get_side(physical_port)

def check_active_linked_tor_side(physical_port):
return _get_side(physical_port)

def check_if_link_is_active_for_NIC(physical_port):
"""
Checks if NIC side of the Y cable's link is active.
Always return True for now because all links in simulator are active.
"""
return True

def check_if_link_is_active_for_torA(physical_port):
"""
Checks if TOR_A side of the Y cable's link is active.
Always return True for now because all links in simulator are active.
"""
return True

def check_if_link_is_active_for_torB(physical_port):
"""
Checks if TOR_B side of the Y cable's link is active.
Always return True for now because all links in simulator are active.
"""
return True

10 changes: 10 additions & 0 deletions ansible/dualtor/y_cable_simulator_injector.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
- name: generate y_cable_simulator driver for {{ dut_name }}
template: src=dualtor/y_cable_simulator_client.j2
dest=/tmp/y_cable_simulator_client.py
delegate_to: "{{ dut_name }}"

- name: inject y_cable_simulator to pmon container for {{ dut_name }}
shell: docker cp /tmp/y_cable_simulator_client.py pmon:/usr/lib/python3/dist-packages
delegate_to: "{{ dut_name }}"

2 changes: 2 additions & 0 deletions ansible/group_vars/all/variables
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mux_simulator_port: 8080
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe we have mux_simulator per topology?


50 changes: 50 additions & 0 deletions ansible/library/vmhost_server_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python

from ansible.parsing.dataloader import DataLoader
from ansible.inventory.manager import InventoryManager

DOCUMENTATION = '''
module: vmhost_server_info.py
short_description: Gather mgmt IP for given host server (like server_17)
Description:
This plugin will parse the input vm_file and return mgmt IP for given host server.
options:
vmhost_server_name: the name of vm_host server, like server_1; required: True
vm_file: the virtual machine file path ; default: 'veos'

Ansible_facts:
'vmhost_server_address': the IPv4 address for given vmhost server

'''

EXAMPLES = '''
- name: gather vm_host server address
vmhost_server_info: vmhost_server_name='server_1' vm_file='veos'
'''

# Here we assume that the group name of host server starts with 'vm_host_'.
VMHOST_PREFIX = "vm_host_"

VM_INV_FILE = 'veos'

def main():
module = AnsibleModule(
argument_spec=dict(
vmhost_server_name=dict(required=True, type='str'),
vm_file=dict(default=VM_INV_FILE, type='str')
),
supports_check_mode=True
)
m_args = module.params
vmhost_group_name = VMHOST_PREFIX + m_args['vmhost_server_name'].split('_')[-1]
inv_mgr = InventoryManager(loader=DataLoader(), sources=m_args['vm_file'])
all_hosts = inv_mgr.get_hosts(pattern=vmhost_group_name)
if len(all_hosts) != 1:
module.fail_json(msg="{} host servers are found in {}, which should be 1".format(len(all_hosts), vmhost_group_name))
else:
module.exit_json(ansible_facts={'vmhost_server_address':all_hosts[0].get_vars()['ansible_host']})

from ansible.module_utils.basic import *
if __name__ == "__main__":
main()