diff --git a/ansible/config_sonic_basedon_testbed.yml b/ansible/config_sonic_basedon_testbed.yml index a028f335a64..d808bc90375 100644 --- a/ansible/config_sonic_basedon_testbed.yml +++ b/ansible/config_sonic_basedon_testbed.yml @@ -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 diff --git a/ansible/dualtor/config_y_cable_simulator.yml b/ansible/dualtor/config_y_cable_simulator.yml new file mode 100644 index 00000000000..2b9b096c1db --- /dev/null +++ b/ansible/dualtor/config_y_cable_simulator.yml @@ -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 + diff --git a/ansible/dualtor/y_cable_simulator_client.j2 b/ansible/dualtor/y_cable_simulator_client.j2 new file mode 100644 index 00000000000..f34c7aeb56e --- /dev/null +++ b/ansible/dualtor/y_cable_simulator_client.j2 @@ -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 + diff --git a/ansible/dualtor/y_cable_simulator_injector.yml b/ansible/dualtor/y_cable_simulator_injector.yml new file mode 100644 index 00000000000..30a1ec32663 --- /dev/null +++ b/ansible/dualtor/y_cable_simulator_injector.yml @@ -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 }}" + diff --git a/ansible/group_vars/all/variables b/ansible/group_vars/all/variables new file mode 100644 index 00000000000..9045048f064 --- /dev/null +++ b/ansible/group_vars/all/variables @@ -0,0 +1,2 @@ +mux_simulator_port: 8080 + diff --git a/ansible/library/vmhost_server_info.py b/ansible/library/vmhost_server_info.py new file mode 100644 index 00000000000..b0dd996a379 --- /dev/null +++ b/ansible/library/vmhost_server_info.py @@ -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() +