Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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

155 changes: 155 additions & 0 deletions ansible/dualtor/y_cable_simulator_client.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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(port):
"""
Helper function to build an url for given port and target

Args:
port: physical port on switch, an integer
Returns:
The url for post/get.
"""
return BASE_URL + "/mux/{}/{}".format(VM_SET, 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:
A dict decoded from server's response. 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
Copy Markdown
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?


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

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

DOCUMENTATION = '''
module: vmhost_server_info.py
Ansible_version_added: 2.0.0.2
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()