diff --git a/ansible/library/conn_graph_facts.py b/ansible/library/conn_graph_facts.py index 0db38699f96..de01ce16e27 100755 --- a/ansible/library/conn_graph_facts.py +++ b/ansible/library/conn_graph_facts.py @@ -233,12 +233,17 @@ def main(): module = AnsibleModule( argument_spec=dict( host=dict(required=False), + hosts=dict(required=False, type='list'), filename=dict(required=False), ), + mutually_exclusive=[['host', 'hosts']], supports_check_mode=True ) m_args = module.params - hostname = m_args['host'] + + hostnames = m_args['hosts'] + if not hostnames: + hostnames = [m_args['host']] try: if m_args['filename']: filename = m_args['filename'] @@ -246,16 +251,31 @@ def main(): filename = LAB_GRAPHFILE_PATH + LAB_CONNECTION_GRAPH_FILE lab_graph = Parse_Lab_Graph(filename) lab_graph.parse_graph() - dev = lab_graph.get_host_device_info(hostname) - if dev is None: - module.fail_json(msg="cannot find info for "+hostname) - results = {} - results['device_info'] = lab_graph.get_host_device_info(hostname) - results['device_conn'] = lab_graph.get_host_connections(hostname) - if lab_graph.get_host_vlan(hostname): - results['device_vlan_range'] = lab_graph.get_host_vlan(hostname)['VlanRange'] - results['device_vlan_list'] = lab_graph.get_host_vlan(hostname)['VlanList'] - results['device_port_vlans'] = lab_graph.get_host_port_vlans(hostname) + + device_info = [] + device_conn = [] + device_port_vlans = [] + device_vlan_range = [] + device_vlan_list = [] + for hostname in hostnames: + dev = lab_graph.get_host_device_info(hostname) + if dev is None: + module.fail_json(msg="cannot find info for %s" % hostname) + device_info.append(dev) + device_conn.append(lab_graph.get_host_connections(hostname)) + host_vlan = lab_graph.get_host_vlan(hostname) + # for multi-DUTs, must ensure all have vlan configured. + if host_vlan: + device_vlan_range.append(host_vlan["VlanRange"]) + device_vlan_list.append(host_vlan["VlanList"]) + device_port_vlans.append(lab_graph.get_host_port_vlans(hostname)) + results = {k: v for k, v in locals().items() + if (k.startswith("device_") and v)} + + # flatten the lists for single host + if m_args['hosts'] is None: + results = {k: v[0] for k, v in results.items()} + module.exit_json(ansible_facts=results) except (IOError, OSError): module.fail_json(msg="Can not find lab graph file "+LAB_CONNECTION_GRAPH_FILE) @@ -264,7 +284,5 @@ def main(): from ansible.module_utils.basic import * -if __name__== "__main__": +if __name__ == "__main__": main() - - diff --git a/ansible/roles/fanout/tasks/rootfanout_connect.yml b/ansible/roles/fanout/tasks/rootfanout_connect.yml index 7994e730031..034686da23d 100644 --- a/ansible/roles/fanout/tasks/rootfanout_connect.yml +++ b/ansible/roles/fanout/tasks/rootfanout_connect.yml @@ -12,6 +12,14 @@ delegate_to: localhost tags: always register: devinfo + when: dut.split(',')|length == 1 + +- name: Gathering connection facts about the DUTs or leaffanout device + conn_graph_facts: hosts={{ dut.split(',') }} + delegate_to: localhost + tags: always + register: devinfo + when: dut.split(',')|length > 1 - name: Gathering connection facts about the lab conn_graph_facts: @@ -20,7 +28,7 @@ register: lab - set_fact: - dev_vlans: "{{ devinfo.ansible_facts.device_vlan_range }}" + dev_vlans: "{{ devinfo.ansible_facts.device_vlan_range|flatten(levels=1) }}" lab_devices: "{{ lab.ansible_facts.device_info }}" - name: Find the root fanout switch diff --git a/ansible/roles/vm_set/library/vlan_port.py b/ansible/roles/vm_set/library/vlan_port.py index 9c6537c72bc..4509d5a2090 100644 --- a/ansible/roles/vm_set/library/vlan_port.py +++ b/ansible/roles/vm_set/library/vlan_port.py @@ -1,5 +1,6 @@ #!/usr/bin/python +import itertools import re import sys import time @@ -121,18 +122,25 @@ def main(): module = AnsibleModule(argument_spec=dict( cmd=dict(required=True, choices=['create', 'remove', 'list']), - external_port = dict(required=True, type='str'), + external_port=dict(required=True, type='str'), vlan_ids=dict(required=True, type='list'), + is_multi_duts=dict(required=False, type='bool', default=False), )) cmd = module.params['cmd'] external_port = module.params['external_port'] vlan_ids = module.params['vlan_ids'] - vlan_ids.sort() + is_multi_duts = module.params['is_multi_duts'] + + _vlan_ids = vlan_ids + if is_multi_duts: + # flatten the list in the case of multi-DUTs + _vlan_ids = list(itertools.chain.from_iterable(_vlan_ids)) + _vlan_ids.sort() fp_ports = [] - vp = VlanPort(external_port, vlan_ids) + vp = VlanPort(external_port, _vlan_ids) vp.up_external_port() if cmd == "create": @@ -140,10 +148,17 @@ def main(): elif cmd == "remove": vp.remove_vlan_ports() - for vlan_id in vlan_ids: - fp_ports.append("%s.%d" % (external_port, vlan_id)) + fp_port_templ = external_port + ".%s" + if is_multi_duts: + fp_ports = [] + for dut_vlans in vlan_ids: + dut_vlans.sort() + fp_ports.append([fp_port_templ % vid for vid in dut_vlans]) + else: + fp_ports = [fp_port_templ % vid for vid in vlan_ids] module.exit_json(changed=False, ansible_facts={'dut_fp_ports': fp_ports}) + if __name__ == "__main__": main() diff --git a/ansible/roles/vm_set/library/vm_topology.py b/ansible/roles/vm_set/library/vm_topology.py index 9019cad74e9..047ec134de7 100644 --- a/ansible/roles/vm_set/library/vm_topology.py +++ b/ansible/roles/vm_set/library/vm_topology.py @@ -108,8 +108,28 @@ cmd_debug_fname = None + +class HostInterfaces(object): + """Data descriptor that supports multi-DUTs interface definition.""" + + def __get__(self, obj, objtype): + return obj._host_interfaces + + def __set__(self, obj, host_interfaces): + """Parse and set host interfaces.""" + if obj._is_multi_duts: + obj._host_interfaces = [] + for intf in host_interfaces: + obj._host_interfaces.append( + tuple(map(int, intf.strip().split(".")))) + else: + obj._host_interfaces = host_interfaces + + class VMTopology(object): + host_interfaces = HostInterfaces() + def __init__(self, vm_names, fp_mtu, max_fp_num): self.vm_names = vm_names self.fp_mtu = fp_mtu @@ -119,7 +139,8 @@ def __init__(self, vm_names, fp_mtu, max_fp_num): return - def init(self, vm_set_name, topo, vm_base, dut_fp_ports, ptf_exists=True): + def init(self, vm_set_name, topo, vm_base, dut_fp_ports, ptf_exists=True, + is_multi_duts=False): self.vm_set_name = vm_set_name self.VMs = {} if 'VMs' in topo: @@ -137,6 +158,7 @@ def init(self, vm_set_name, topo, vm_base, dut_fp_ports, ptf_exists=True): if len(attrs['vlans']) > len(self.get_bridges(vmname)): raise Exception("Wrong vlans parameter for hostname %s, vm %s. Too many vlans. Maximum is %d" % (hostname, vmname, len(self.get_bridges(vmname)))) + self._is_multi_duts = is_multi_duts if 'host_interfaces' in topo: self.host_interfaces = topo['host_interfaces'] else: @@ -470,16 +492,28 @@ def unbind_ovs_port(self, br_name, port): def inject_host_ports(self): """inject dut port into the ptf docker""" self.update() - for vlan in self.host_interfaces: - self.add_dut_if_to_docker(PTF_FP_IFACE_TEMPLATE % vlan, self.dut_fp_ports[vlan]) + for i, intf in enumerate(self.host_interfaces): + if self._is_multi_duts: + fp_port = self.dut_fp_ports[intf[0]][intf[1]] + ptf_intf = PTF_FP_IFACE_TEMPLATE % i + else: + fp_port = self.dut_fp_ports[intf] + ptf_intf = PTF_FP_IFACE_TEMPLATE % intf + self.add_dut_if_to_docker(ptf_intf, fp_port) return def deject_host_ports(self): """deject dut port from the ptf docker""" self.update() - for vlan in self.host_interfaces: - self.remove_dut_if_from_docker(PTF_FP_IFACE_TEMPLATE % vlan, self.dut_fp_ports[vlan]) + for i, intf in enumerate(self.host_interfaces): + if self._is_multi_duts: + fp_port = self.dut_fp_ports[intf[0]][intf[1]] + ptf_intf = PTF_FP_IFACE_TEMPLATE % i + else: + fp_port = self.dut_fp_ports[intf] + ptf_intf = PTF_FP_IFACE_TEMPLATE % intf + self.remove_dut_if_from_docker(ptf_intf, fp_port) @staticmethod def iface_up(iface_name, pid=None): @@ -610,7 +644,13 @@ def brctl_show(): return br_to_ifs, if_to_br -def check_topo(topo): + +def check_topo(topo, is_multi_duts=False): + + def _assert(condition, exctype, msg): + if not condition: + raise exctype(msg) + hostif_exists = False vms_exists = False all_vlans = set() @@ -618,44 +658,61 @@ def check_topo(topo): if 'host_interfaces' in topo: vlans = topo['host_interfaces'] - if not isinstance(vlans, list): - raise Exception("topo['host_interfaces'] should be a list of integers") + _assert(isinstance(vlans, list), TypeError, + "topo['host_interfaces'] should be a list") for vlan in vlans: - if not isinstance(vlan, int) or vlan < 0: - raise Exception("topo['host_interfaces'] should be a list of integers") - if vlan in all_vlans: - raise Exception("topo['host_interfaces'] double use of vlan: %d" % vlan) + if is_multi_duts: + condition = (isinstance(vlan, str) and + re.match(r"\d+.\d+", vlan)) + _assert(condition, ValueError, + "topo['host_interfaces'] should be a " + "list of strings of format '.'") else: - all_vlans.add(vlan) + condition = isinstance(vlan, int) and vlan >= 0 + _assert(condition, ValueError, + "topo['host_interfaces'] should be a " + "list of positive integers") + _assert(vlan not in all_vlans, ValueError, + "topo['host_interfaces'] double use of vlan: %s" % vlan) + all_vlans.add(vlan) hostif_exists = True if 'VMs' in topo: VMs = topo['VMs'] - if not isinstance(VMs, dict): - raise Exception("topo['VMs'] should be a dictionary") + _assert(isinstance(VMs, dict), TypeError, + "topo['VMs'] should be a dictionary") for hostname, attrs in VMs.items(): - if 'vlans' not in attrs or not isinstance(attrs['vlans'], list): - raise Exception("topo['VMs']['%s'] should contain 'vlans' with a list of vlans" % hostname) + _assert('vlans' in attrs and isinstance(attrs['vlans'], list), + ValueError, + "topo['VMs']['%s'] should contain " + "'vlans' with a list of vlans" % hostname) - if 'vm_offset' not in attrs or not isinstance(attrs['vm_offset'], int): - raise Exception("topo['VMs']['%s'] should contain 'vm_offset' with a number" % hostname) + _assert(('vm_offset' in attrs and + isinstance(attrs['vm_offset'], int)), + ValueError, + "topo['VMs']['%s'] should contain " + "'vm_offset' with a number" % hostname) for vlan in attrs['vlans']: - if not isinstance(vlan, int) or vlan < 0: - raise Exception("topo['VMs'][%s]['vlans'] should contain a list with integers" % hostname) - if vlan in all_vlans: - raise Exception("topo['VMs'][%s]['vlans'] double use of vlan: %d" % (hostname, vlan)) - else: - all_vlans.add(vlan) + _assert(isinstance(vlan, int) and vlan >= 0, + ValueError, + "topo['VMs'][%s]['vlans'] should contain" + " a list with integers" % hostname) + _assert(vlan not in all_vlans, + ValueError, + "topo['VMs'][%s]['vlans'] double use " + "of vlan: %s" % (hostname, vlan)) + all_vlans.add(vlan) vms_exists = True return hostif_exists, vms_exists + def check_params(module, params, mode): for param in params: if param not in module.params: @@ -663,6 +720,7 @@ def check_params(module, params, mode): return + def main(): module = AnsibleModule( argument_spec=dict( @@ -680,6 +738,7 @@ def main(): dut_mgmt_port=dict(required=False, type='str'), fp_mtu=dict(required=False, type='int', default=DEFAULT_MTU), max_fp_num=dict(required=False, type='int', default=NUM_FP_VLANS_PER_FP), + is_multi_duts=dict(required=False, type='bool', default=False), ), supports_check_mode=False) @@ -718,11 +777,12 @@ def main(): vm_set_name = module.params['vm_set_name'] topo = module.params['topo'] dut_fp_ports = module.params['dut_fp_ports'] + is_multi_duts = module.params['is_multi_duts'] if len(vm_set_name) > VM_SET_NAME_MAX_LEN: raise Exception("vm_set_name can't be longer than %d characters: %s (%d)" % (VM_SET_NAME_MAX_LEN, vm_set_name, len(vm_set_name))) - hostif_exists, vms_exists = check_topo(topo) + hostif_exists, vms_exists = check_topo(topo, is_multi_duts) if vms_exists: check_params(module, ['vm_base'], cmd) @@ -730,7 +790,8 @@ def main(): else: vm_base = None - net.init(vm_set_name, topo, vm_base, dut_fp_ports) + net.init(vm_set_name, topo, vm_base, dut_fp_ports, + is_multi_duts=is_multi_duts) ptf_mgmt_ip_addr = module.params['ptf_mgmt_ip_addr'] ptf_mgmt_ip_gw = module.params['ptf_mgmt_ip_gw'] @@ -760,11 +821,12 @@ def main(): vm_set_name = module.params['vm_set_name'] topo = module.params['topo'] dut_fp_ports = module.params['dut_fp_ports'] + is_multi_duts = module.params['is_multi_duts'] if len(vm_set_name) > VM_SET_NAME_MAX_LEN: raise Exception("vm_set_name can't be longer than %d characters: %s (%d)" % (VM_SET_NAME_MAX_LEN, vm_set_name, len(vm_set_name))) - hostif_exists, vms_exists = check_topo(topo) + hostif_exists, vms_exists = check_topo(topo, is_multi_duts) if vms_exists: check_params(module, ['vm_base'], cmd) @@ -772,7 +834,8 @@ def main(): else: vm_base = None - net.init(vm_set_name, topo, vm_base, dut_fp_ports) + net.init(vm_set_name, topo, vm_base, dut_fp_ports, + is_multi_duts=is_multi_duts) if module.params['dut_mgmt_port']: net.unbind_mgmt_port(module.params['dut_mgmt_port']) @@ -796,11 +859,12 @@ def main(): vm_set_name = module.params['vm_set_name'] topo = module.params['topo'] dut_fp_ports = module.params['dut_fp_ports'] + is_multi_duts = module.params['is_multi_duts'] if len(vm_set_name) > VM_SET_NAME_MAX_LEN: raise Exception("vm_set_name can't be longer than %d characters: %s (%d)" % (VM_SET_NAME_MAX_LEN, vm_set_name, len(vm_set_name))) - hostif_exists, vms_exists = check_topo(topo) + hostif_exists, vms_exists = check_topo(topo, is_multi_duts) if vms_exists: check_params(module, ['vm_base'], cmd) @@ -808,7 +872,8 @@ def main(): else: vm_base = None - net.init(vm_set_name, topo, vm_base, dut_fp_ports, True) + net.init(vm_set_name, topo, vm_base, dut_fp_ports, True, + is_multi_duts) ptf_mgmt_ip_addr = module.params['ptf_mgmt_ip_addr'] ptf_mgmt_ip_gw = module.params['ptf_mgmt_ip_gw'] @@ -833,11 +898,12 @@ def main(): vm_set_name = module.params['vm_set_name'] topo = module.params['topo'] dut_fp_ports = module.params['dut_fp_ports'] + is_multi_duts = module.params['is_multi_duts'] if len(vm_set_name) > VM_SET_NAME_MAX_LEN: raise Exception("vm_set_name can't be longer than %d characters: %s (%d)" % (VM_SET_NAME_MAX_LEN, vm_set_name, len(vm_set_name))) - hostif_exists, vms_exists = check_topo(topo) + hostif_exists, vms_exists = check_topo(topo, is_multi_duts) if vms_exists: check_params(module, ['vm_base'], cmd) @@ -845,7 +911,8 @@ def main(): else: vm_base = None - net.init(vm_set_name, topo, vm_base, dut_fp_ports) + net.init(vm_set_name, topo, vm_base, dut_fp_ports, + is_multi_duts=is_multi_duts) if vms_exists: if cmd == 'connect-vms': diff --git a/ansible/roles/vm_set/tasks/add_topo.yml b/ansible/roles/vm_set/tasks/add_topo.yml index 8b1dd6eb8e4..9011e176edd 100644 --- a/ansible/roles/vm_set/tasks/add_topo.yml +++ b/ansible/roles/vm_set/tasks/add_topo.yml @@ -42,6 +42,7 @@ vlan_port: external_port: "{{ external_port }}" vlan_ids: "{{ device_vlan_list }}" + is_multi_duts: "{{ dut_name is defined and dut_name.split(',')|length > 1 }}" cmd: "create" become: yes when: external_port is defined @@ -65,6 +66,7 @@ dut_mgmt_port: "{{ dut_mgmt_port }}" fp_mtu: "{{ fp_mtu_size }}" max_fp_num: "{{ max_fp_num }}" + is_multi_duts: "{{ dut_name is defined and dut_name.split(',')|length > 1 }}" become: yes - name: Send arp ping packet to gw for flusing the ARP table diff --git a/ansible/roles/vm_set/tasks/main.yml b/ansible/roles/vm_set/tasks/main.yml index ba4b7c100db..34f68c3899f 100644 --- a/ansible/roles/vm_set/tasks/main.yml +++ b/ansible/roles/vm_set/tasks/main.yml @@ -241,5 +241,6 @@ when: action == 'stop_sid' and hostvars[dut_name]['type'] == 'simx' when: - dut_name is defined + - dut_name.split(',')|length == 1 - hostvars[dut_name] is defined - hostvars[dut_name].type is defined diff --git a/ansible/roles/vm_set/tasks/remove_topo.yml b/ansible/roles/vm_set/tasks/remove_topo.yml index 622282f5141..910224c4767 100644 --- a/ansible/roles/vm_set/tasks/remove_topo.yml +++ b/ansible/roles/vm_set/tasks/remove_topo.yml @@ -11,6 +11,7 @@ dut_fp_ports: "{{ dut_fp_ports }}" dut_mgmt_port: "{{ dut_mgmt_port }}" max_fp_num: "{{ max_fp_num }}" + is_multi_duts: "{{ dut_name is defined and dut_name.split(',')|length > 1 }}" become: yes - include_tasks: remove_ceos_list.yml @@ -20,6 +21,7 @@ vlan_port: external_port: "{{ external_port }}" vlan_ids: "{{ device_vlan_list }}" + is_multi_duts: "{{ dut_name is defined and dut_name.split(',')|length > 1 }}" cmd: "remove" become: yes when: external_port is defined diff --git a/ansible/roles/vm_set/tasks/set_dut_port.yml b/ansible/roles/vm_set/tasks/set_dut_port.yml index 93f912f9325..b8ed60f385f 100644 --- a/ansible/roles/vm_set/tasks/set_dut_port.yml +++ b/ansible/roles/vm_set/tasks/set_dut_port.yml @@ -2,6 +2,7 @@ vlan_port: external_port: "{{ external_port }}" vlan_ids: "{{ device_vlan_list }}" + is_multi_duts: "{{ dut_name is defined and dut_name.split(',')|length > 1 }}" cmd: "list" become: yes when: external_port is defined diff --git a/ansible/testbed_add_vm_topology.yml b/ansible/testbed_add_vm_topology.yml index d8865b61134..31fdf12ad44 100644 --- a/ansible/testbed_add_vm_topology.yml +++ b/ansible/testbed_add_vm_topology.yml @@ -69,8 +69,16 @@ include_vars: "vars/topo_{{ topo }}.yml" - name: Read dut minigraph - conn_graph_facts: host={{ dut_name }} + conn_graph_facts: + host: "{{ dut_name }}" delegate_to: localhost + when: dut_name.split(',')|length == 1 + + - name: Read duts minigraph + conn_graph_facts: + hosts: "{{ dut_name.split(',') }}" + delegate_to: localhost + when: dut_name.split(',')|length > 1 roles: - { role: vm_set, action: 'start_sonic_vm' } diff --git a/ansible/testbed_remove_vm_topology.yml b/ansible/testbed_remove_vm_topology.yml index 9a83ee1610b..4f9f9c67f2a 100644 --- a/ansible/testbed_remove_vm_topology.yml +++ b/ansible/testbed_remove_vm_topology.yml @@ -51,8 +51,16 @@ include_vars: "vars/topo_{{ topo }}.yml" - name: Read dut minigraph - conn_graph_facts: host={{ dut_name }} + conn_graph_facts: + host: "{{ dut_name }}" delegate_to: localhost + when: dut_name.split(',')|length == 1 + + - name: Read duts minigraph + conn_graph_facts: + hosts: "{{ dut_name.split(',') }}" + delegate_to: localhost + when: dut_name.split(',')|length > 1 roles: - { role: vm_set, action: 'remove_topo' } diff --git a/ansible/vars/topo_fullmesh.yml b/ansible/vars/topo_fullmesh.yml new file mode 100644 index 00000000000..902f965e8fe --- /dev/null +++ b/ansible/vars/topo_fullmesh.yml @@ -0,0 +1,18 @@ +topology: + host_interfaces: + - "0.0" + - "0.1" + - "0.2" + - "0.3" + - "1.0" + - "1.1" + - "1.2" + - "1.3" + - "2.0" + - "2.1" + - "2.2" + - "2.3" + - "3.0" + - "3.1" + - "3.2" + - "3.3"