diff --git a/ansible/config_sonic_basedon_testbed.yml b/ansible/config_sonic_basedon_testbed.yml index f023ce7942b..46491b5acec 100644 --- a/ansible/config_sonic_basedon_testbed.yml +++ b/ansible/config_sonic_basedon_testbed.yml @@ -71,7 +71,7 @@ - name: set vm set_fact: - vm_base: "{% if testbed_facts['vm_base'] != '' %}{{ testbed_facts['vm_base'] }}{% else %}''{% endif %}" + vm_base: "{% if 'vm_base' in testbed_facts and testbed_facts['vm_base'] != '' %}{{ testbed_facts['vm_base'] }}{% else %}''{% endif %}" when: testbed_name is defined - name: find supervisor dut of testbed @@ -224,7 +224,7 @@ remote_dut: "{{ ansible_ssh_host }}" - name: gather testbed VM information - testbed_vm_info: base_vm={{ testbed_facts['vm_base'] }} topo={{ testbed_facts['topo'] }} vm_file={{ vm_file }} + testbed_vm_info: base_vm={{ testbed_facts['vm_base'] if 'vm_base' in testbed_facts else "" }} topo={{ testbed_facts['topo'] }} vm_file={{ vm_file }} servers_info={{ testbed_facts['servers'] | default({}) }} delegate_to: localhost when: "(VM_topo | bool) and ('cable' not in topo)" @@ -435,7 +435,7 @@ - name: Update TACACS server address to PTF IP set_fact: - tacacs_servers: ["{{ testbed_facts['ptf_ip'] }}"] + tacacs_servers: ["{{ testbed_facts['multi_servers_tacacs_ip'] if 'multi_servers_tacacs_ip' in testbed_facts else testbed_facts['ptf_ip'] }}"] when: use_ptf_tacacs_server is defined and use_ptf_tacacs_server|bool == true - debug: msg="tacacs_servers {{ tacacs_servers }}" diff --git a/ansible/fanout_connect.yml b/ansible/fanout_connect.yml index 07d36951b4a..063c92d5a72 100644 --- a/ansible/fanout_connect.yml +++ b/ansible/fanout_connect.yml @@ -11,6 +11,7 @@ - set_fact: server: "{{ inventory_hostname|lower }}" server_port: "{{ external_port }}" + clean_before_add: "{{ clean_before_add | default('y') }}" - set_fact: root_fanout_connect=true when: root_fanout_connect is not defined diff --git a/ansible/library/announce_routes.py b/ansible/library/announce_routes.py index ec80c81e483..c6cb58640b9 100644 --- a/ansible/library/announce_routes.py +++ b/ansible/library/announce_routes.py @@ -15,6 +15,7 @@ from multiprocessing.pool import ThreadPool from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.debug_utils import config_module_logging +from ansible.module_utils.multi_servers_utils import MultiServersUtils if sys.version_info.major == 3: UNICODE_TYPE = str @@ -1150,6 +1151,7 @@ def main(): action=dict(required=False, type='str', default='announce', choices=["announce", "withdraw"]), path=dict(required=False, type='str', default=''), + dut_interfaces=dict(required=False, type='str', default=''), log_path=dict(required=False, type='str', default='') ), supports_check_mode=False) @@ -1160,11 +1162,17 @@ def main(): topo_name = module.params['topo_name'] ptf_ip = module.params['ptf_ip'] action = module.params['action'] + dut_interfaces = module.params['dut_interfaces'] path = module.params['path'] topo = read_topo(topo_name, path) if not topo: module.fail_json(msg='Unable to load topology "{}"'.format(topo_name)) + if dut_interfaces: + topo['topology']['VMs'] = MultiServersUtils.get_vms_by_dut_interfaces(topo['topology']['VMs'], dut_interfaces) + for vm_name in topo['configuration'].keys(): + if vm_name not in topo['topology']['VMs']: + topo['configuration'].pop(vm_name) is_storage_backend = "backend" in topo_name diff --git a/ansible/library/test_facts.py b/ansible/library/test_facts.py index e915babccca..0c73756f009 100644 --- a/ansible/library/test_facts.py +++ b/ansible/library/test_facts.py @@ -169,15 +169,18 @@ def _read_testbed_topo_from_yaml(): with open(self.testbed_filename) as f: tb_info = yaml.safe_load(f) for tb in tb_info: - if tb["ptf_ip"]: + if "ptf_ip" in tb and tb["ptf_ip"]: tb["ptf_ip"], tb["ptf_netmask"] = \ _cidr_to_ip_mask(tb["ptf_ip"]) - if tb["ptf_ipv6"]: + if "ptf_ipv6" in tb and tb["ptf_ipv6"]: tb["ptf_ipv6"], tb["ptf_netmask_v6"] = \ _cidr_to_ip_mask(tb["ptf_ipv6"]) tb["duts"] = tb.pop("dut") tb["duts_map"] = \ {dut: i for i, dut in enumerate(tb["duts"])} + if 'servers' in tb: + tb['multi_servers_tacacs_ip'], _ = \ + _cidr_to_ip_mask(tb['servers'].values()[0]["ptf_ip"]) self.testbed_topo[tb["conf-name"]] = tb if self.testbed_filename.endswith(".csv"): diff --git a/ansible/library/testbed_vm_info.py b/ansible/library/testbed_vm_info.py index 61898d5c22e..3b91e9bfb9c 100644 --- a/ansible/library/testbed_vm_info.py +++ b/ansible/library/testbed_vm_info.py @@ -1,6 +1,7 @@ #!/usr/bin/env python from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.multi_servers_utils import MultiServersUtils import re import yaml import traceback @@ -49,12 +50,13 @@ class TestbedVMFacts(): """ - def __init__(self, toponame, base_vm, vm_file): + def __init__(self, toponame, base_vm, vm_file, servers_info): CLET_SUFFIX = "-clet" self.toponame = re.sub(CLET_SUFFIX + "$", "", toponame) self.topofile = TOPO_PATH + 'topo_' + self.toponame + '.yml' self.base_vm = base_vm self.vm_file = vm_file + self.servers_info = servers_info if has_dataloader: self.inv_mgr = InventoryManager( loader=DataLoader(), sources=self.vm_file) @@ -65,6 +67,12 @@ def get_neighbor_eos(self): vm_topology = yaml.safe_load(f) self.topoall = vm_topology + if self.servers_info: + return MultiServersUtils.generate_vm_name_mapping( + self.servers_info, + vm_topology['topology']['VMs'] + ) + if len(self.base_vm) > 2: vm_start_index = int(self.base_vm[2:]) vm_name_fmt = 'VM%0{}d'.format(len(self.base_vm) - 2) @@ -116,6 +124,7 @@ def main(): argument_spec=dict( base_vm=dict(required=True, type='str'), topo=dict(required=True, type='str'), + servers_info=dict(required=False, type='dict', default={}), vm_file=dict(default=VM_INV_FILE, type='str') ), supports_check_mode=True @@ -128,7 +137,7 @@ def main(): vm_mgmt_ip = {} try: vm_facts = TestbedVMFacts( - m_args['topo'], m_args['base_vm'], m_args['vm_file']) + m_args['topo'], m_args['base_vm'], m_args['vm_file'], m_args['servers_info']) neighbor_eos = vm_facts.get_neighbor_eos() neighbor_eos.update(vm_facts.get_neighbor_dpu()) if has_dataloader: diff --git a/ansible/module_utils/multi_servers_utils.py b/ansible/module_utils/multi_servers_utils.py new file mode 100644 index 00000000000..0fa89aa999a --- /dev/null +++ b/ansible/module_utils/multi_servers_utils.py @@ -0,0 +1,66 @@ +class MultiServersUtils: + @staticmethod + def filter_by_dut_interfaces(values, dut_interfaces): + if not dut_interfaces: + return values + + if isinstance(dut_interfaces, str) or isinstance(dut_interfaces, unicode): # noqa F821 + dut_interfaces = MultiServersUtils.parse_multi_servers_interface(dut_interfaces) + + if isinstance(values, dict): + return {k: v for k, v in values.items() if int(k) in dut_interfaces} + elif isinstance(values, list): + return [v for v in values if int(v) in dut_interfaces] + else: + raise ValueError('Unsupported type "{}"'.format(type(values))) + + @staticmethod + def parse_multi_servers_interface(intf_pattern): + intf_pattern = str(intf_pattern) + intfs = [] + for intf in iter(map(str.strip, intf_pattern.split(','))): + if intf.isdigit(): + intfs.append(int(intf)) + elif '-' in intf: + intf_range = list(map(int, map(str.strip, intf.split('-')))) + assert len(intf_range) == 2, 'Invalid interface range "{}"'.format(intf) + intfs.extend(list(range(intf_range[0], intf_range[1]+1))) + else: + raise ValueError('Unsupported format "{}"'.format(intf_pattern)) + if len(intfs) != len(set(intfs)): + raise ValueError('There are interface duplication/overlap in "{}"'.format(intf_pattern)) + return intfs + + @staticmethod + def get_vms_by_dut_interfaces(VMs, dut_interfaces): + if not dut_interfaces: + return VMs + + if isinstance(dut_interfaces, str) or isinstance(dut_interfaces, unicode): # noqa F821 + dut_interfaces = MultiServersUtils.parse_multi_servers_interface(dut_interfaces) + + result = {} + offset = 0 + for hostname, attr in VMs.items(): + if dut_interfaces and attr['vlans'][0] not in dut_interfaces: + continue + result[hostname] = attr + result[hostname]['vm_offset'] = offset + offset += 1 + return result + + @staticmethod + def generate_vm_name_mapping(servers_info, topo_vms): + _m = {} + + for server_attr in servers_info.values(): + if 'dut_interfaces' in server_attr: + filtered_vms = MultiServersUtils.get_vms_by_dut_interfaces(topo_vms, server_attr['dut_interfaces']) + vm_base = server_attr['vm_base'] + vm_start_index = int(vm_base[2:]) + vm_name_fmt = 'VM%0{}d'.format(len(vm_base) - 2) + + for hostname, host_attr in filtered_vms.items(): + vm_name = vm_name_fmt % (vm_start_index + host_attr['vm_offset']) + _m[hostname] = vm_name + return _m diff --git a/ansible/plugins/filter/filters.py b/ansible/plugins/filter/filters.py index 4a9474db371..733095737fd 100644 --- a/ansible/plugins/filter/filters.py +++ b/ansible/plugins/filter/filters.py @@ -8,6 +8,8 @@ class FilterModule(object): def filters(self): return { + 'filter_by_dut_interfaces': MultiServersUtils.filter_by_dut_interfaces, + 'get_vms_by_dut_interfaces': MultiServersUtils.get_vms_by_dut_interfaces, 'extract_by_prefix': extract_by_prefix, 'filter_by_prefix': filter_by_prefix, 'filter_vm_targets': filter_vm_targets, @@ -88,7 +90,7 @@ def first_n_elements(values, num): return values[0:int(num)] -def filter_vm_targets(values, topology, vm_base): +def filter_vm_targets(values, topology, vm_base, dut_interfaces=None): """ This function takes a list of host VMs as parameter 'values' and then extract a list of host VMs which starts with 'vm_base' and contains all VMs which mentioned in 'vm_offset' keys inside of 'topology' structure @@ -114,9 +116,10 @@ def filter_vm_targets(values, topology, vm_base): if vm_base not in values: raise errors.AnsibleFilterError('Current vm_base: %s is not found in vm_list' % vm_base) + vms = MultiServersUtils.get_vms_by_dut_interfaces(topology, dut_interfaces) if dut_interfaces else topology result = [] base = values.index(vm_base) - for hostname, attr in topology.items(): + for hostname, attr in vms.items(): if base + attr['vm_offset'] >= len(values): continue result.append(values[base + attr['vm_offset']]) @@ -124,7 +127,7 @@ def filter_vm_targets(values, topology, vm_base): return result -def extract_hostname(values, topology, vm_base, inventory_hostname): +def extract_hostname(values, topology, vm_base, inventory_hostname, dut_interfaces=None): """ This function takes a list of host VMs as parameter 'values' and then return 'inventory_hostname' corresponding EOS hostname based on 'topology' structure, 'vm_base' parameters @@ -156,8 +159,9 @@ def extract_hostname(values, topology, vm_base, inventory_hostname): if vm_base not in values: raise errors.AnsibleFilterError('Current vm_base: %s is not found in vm_list' % vm_base) + vms = MultiServersUtils.get_vms_by_dut_interfaces(topology, dut_interfaces) if dut_interfaces else topology base = values.index(vm_base) - for hostname, attr in topology.items(): + for hostname, attr in vms.items(): if base + attr['vm_offset'] >= len(values): continue if inventory_hostname == values[base + attr['vm_offset']]: @@ -209,3 +213,55 @@ def first_ip_of_subnet(value): def path_join(paths): """Join path strings.""" return os.path.join(*paths) + + +class MultiServersUtils: + @staticmethod + def filter_by_dut_interfaces(values, dut_interfaces): + if not dut_interfaces: + return values + + if isinstance(dut_interfaces, str) or isinstance(dut_interfaces, unicode): # noqa F821 + dut_interfaces = MultiServersUtils.parse_multi_servers_interface(dut_interfaces) + + if isinstance(values, dict): + return {k: v for k, v in values.items() if int(k) in dut_interfaces} + elif isinstance(values, list): + return [v for v in values if int(v) in dut_interfaces] + else: + raise ValueError('Unsupported type "{}"'.format(type(values))) + + @staticmethod + def parse_multi_servers_interface(intf_pattern): + intf_pattern = str(intf_pattern) + intfs = [] + for intf in iter(map(str.strip, intf_pattern.split(','))): + if intf.isdigit(): + intfs.append(int(intf)) + elif '-' in intf: + intf_range = list(map(int, map(str.strip, intf.split('-')))) + assert len(intf_range) == 2, 'Invalid interface range "{}"'.format(intf) + intfs.extend(list(range(intf_range[0], intf_range[1]+1))) + else: + raise ValueError('Unsupported format "{}"'.format(intf_pattern)) + if len(intfs) != len(set(intfs)): + raise ValueError('There are interface duplication/overlap in "{}"'.format(intf_pattern)) + return intfs + + @staticmethod + def get_vms_by_dut_interfaces(VMs, dut_interfaces): + if not dut_interfaces: + return VMs + + if isinstance(dut_interfaces, str) or isinstance(dut_interfaces, unicode): # noqa F821 + dut_interfaces = MultiServersUtils.parse_multi_servers_interface(dut_interfaces) + + result = {} + offset = 0 + for hostname, attr in VMs.items(): + if dut_interfaces and attr['vlans'][0] not in dut_interfaces: + continue + result[hostname] = attr + result[hostname]['vm_offset'] = offset + offset += 1 + return result diff --git a/ansible/roles/eos/tasks/main.yml b/ansible/roles/eos/tasks/main.yml index 9968faef920..a9115507c28 100644 --- a/ansible/roles/eos/tasks/main.yml +++ b/ansible/roles/eos/tasks/main.yml @@ -17,7 +17,7 @@ set_fact: VM_host={{ groups[current_server] | difference(VM_list) }} - name: Generate hostname for target VM - set_fact: hostname={{ VM_list | extract_hostname(topology['VMs'], VM_base, inventory_hostname) }} + set_fact: hostname={{ VM_list | extract_hostname(topology['VMs'], VM_base, inventory_hostname, dut_interfaces | default("")) }} when: topology['VMs'] is defined - fail: diff --git a/ansible/roles/fanout/templates/arista_7260_connect.j2 b/ansible/roles/fanout/templates/arista_7260_connect.j2 index 94d6003194c..543a4cf78f3 100644 --- a/ansible/roles/fanout/templates/arista_7260_connect.j2 +++ b/ansible/roles/fanout/templates/arista_7260_connect.j2 @@ -10,8 +10,10 @@ vlan {{ dev_vlans | list | join(',') }} {% set peer_speed = root_conn[intf]['speed'] %} {% if peer_dev in lab_devices and 'Fanout' not in lab_devices[peer_dev]['Type'] and not deploy_leaf %} interface {{ intf }} +{% if clean_before_add == 'y' %} switchport switchport trunk allowed vlan remove {{ dev_vlans | list | join(',') }} +{% endif %} {% if peer_dev == server and peer_port == server_port %} switchport mode trunk switchport trunk allowed vlan add {{ dev_vlans | list | join(',') }} @@ -20,8 +22,10 @@ interface {{ intf }} {% endif %} {% if peer_dev in lab_devices and 'Fanout' in lab_devices[peer_dev]['Type'] and deploy_leaf %} interface {{ intf }} +{% if clean_before_add == 'y' %} switchport switchport trunk allowed vlan remove {{ dev_vlans | list | join(',') }} +{% endif %} {% if peer_dev == leaf_name %} description {{ peer_dev }}-{{ peer_port }} speed forced {{ peer_speed }}full diff --git a/ansible/roles/vm_set/library/vm_topology.py b/ansible/roles/vm_set/library/vm_topology.py index c2b64c1cda2..0e07ff3d2d1 100644 --- a/ansible/roles/vm_set/library/vm_topology.py +++ b/ansible/roles/vm_set/library/vm_topology.py @@ -18,8 +18,10 @@ import six from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.multi_servers_utils import MultiServersUtils from logging.handlers import MemoryHandler + try: from ansible.module_utils.dualtor_utils import generate_mux_cable_facts except ImportError: @@ -226,12 +228,13 @@ def adaptive_temporary_interface(vm_set_name, interface_name, reserved_space=0): class VMTopology(object): - def __init__(self, vm_names, vm_properties, fp_mtu, max_fp_num, topo, worker, is_dpu=False): + def __init__(self, vm_names, vm_properties, fp_mtu, max_fp_num, topo, worker, is_dpu=False, dut_interfaces=None): self.vm_names = vm_names self.vm_properties = vm_properties self.fp_mtu = fp_mtu self.max_fp_num = max_fp_num self.topo = topo + self.dut_interfaces = dut_interfaces self._host_interfaces = None self._disabled_host_interfaces = None self._host_interfaces_active_active = None @@ -257,7 +260,11 @@ def init(self, vm_set_name, vm_base, duts_fp_ports, duts_name, ptf_exists=True, else: raise Exception('VM_base "%s" should be presented in current vm_names: %s' % ( vm_base, str(self.vm_names))) - for k, v in self.topo['VMs'].items(): + topo_vms = self.topo['VMs'] + if self.dut_interfaces: + topo_vms = MultiServersUtils.get_vms_by_dut_interfaces(topo_vms, self.dut_interfaces) + + for k, v in topo_vms.items(): if self.vm_base_index + v['vm_offset'] < len(self.vm_names): self.VMs[k] = v else: @@ -297,6 +304,11 @@ def init(self, vm_set_name, vm_base, duts_fp_ports, duts_name, ptf_exists=True, self.duts_name) > 1 and 'VMs' not in self.topo else False self.host_interfaces = self.topo.get('host_interfaces', []) + if self.dut_interfaces: + self.host_interfaces = MultiServersUtils.filter_by_dut_interfaces( + self.host_interfaces, + self.dut_interfaces + ) self.disabled_host_interfaces = self.topo.get( 'disabled_host_interfaces', []) self.host_interfaces_active_active = self.topo.get( @@ -2092,6 +2104,7 @@ def main(): duts_fp_ports=dict(required=False, type='dict'), duts_mgmt_port=dict(required=False, type='list'), duts_name=dict(required=False, type='list'), + dut_interfaces=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), @@ -2111,6 +2124,7 @@ def main(): max_fp_num = module.params['max_fp_num'] vm_properties = module.params['vm_properties'] is_dpu = module.params['is_dpu'] if 'is_dpu' in module.params else False + dut_interfaces = module.params['dut_interfaces'] use_thread_worker = module.params['use_thread_worker'] thread_worker_count = module.params['thread_worker_count'] @@ -2123,7 +2137,7 @@ def main(): topo = module.params['topo'] worker = VMTopologyWorker(use_thread_worker, thread_worker_count) - net = VMTopology(vm_names, vm_properties, fp_mtu, max_fp_num, topo, worker, is_dpu) + net = VMTopology(vm_names, vm_properties, fp_mtu, max_fp_num, topo, worker, is_dpu, dut_interfaces) if cmd == 'create': net.create_bridges() diff --git a/ansible/roles/vm_set/tasks/add_topo.yml b/ansible/roles/vm_set/tasks/add_topo.yml index 95a52d7141a..7727d1e051e 100644 --- a/ansible/roles/vm_set/tasks/add_topo.yml +++ b/ansible/roles/vm_set/tasks/add_topo.yml @@ -233,6 +233,7 @@ fp_mtu: "{{ fp_mtu_size }}" max_fp_num: "{{ max_fp_num }}" netns_mgmt_ip_addr: "{{ netns_mgmt_ip if netns_mgmt_ip is defined else omit }}" + dut_interfaces: "{{ dut_interfaces | default('') }}" become: yes - name: Bind topology {{ topo }} to DPUs. diff --git a/ansible/roles/vm_set/tasks/announce_routes.yml b/ansible/roles/vm_set/tasks/announce_routes.yml index 71e770ef512..4718080dec0 100644 --- a/ansible/roles/vm_set/tasks/announce_routes.yml +++ b/ansible/roles/vm_set/tasks/announce_routes.yml @@ -1,4 +1,3 @@ ---- - name: Set ptf host set_fact: ptf_host: "{{ ptf_ip.split('/')[0] }}" @@ -15,6 +14,10 @@ exabgp_action: start when: exabgp_action is not defined +- name: Get VMs information + set_fact: + topo_vms: "{{ topology['VMs'] | get_vms_by_dut_interfaces(dut_interfaces | default('')) }}" + - block: - name: Configure and start exabpg process for IPV4 when: configuration_properties.common.enable_ipv4_routes_generation | default(True) @@ -33,7 +36,7 @@ local_asn: "{{ configuration[vm_item.key].bgp.asn }}" peer_asn: "{{ configuration[vm_item.key].bgp.asn }}" port: "{{ 5000 + vm_item.value.vm_offset|int }}" - loop: "{{ topology['VMs']|dict2items }}" + loop: "{{ topo_vms|dict2items }}" loop_control: loop_var: vm_item delegate_to: "{{ ptf_host }}" @@ -41,7 +44,7 @@ - name: Gather exabgp v4 programs set_fact: program_group_name: "exabgpv4" - program_group_programs: "{{ topology['VMs'].keys() | map('regex_replace', '^(.*)$', 'exabgp-\\1') | join(',')}}" + program_group_programs: "{{ topo_vms.keys() | map('regex_replace', '^(.*)$', 'exabgp-\\1') | join(',')}}" - name: Configure exabgpv4 group template: @@ -50,7 +53,7 @@ delegate_to: "{{ ptf_host }}" - name: Add exabgpv4 supervisor config and start related processes - when: "{{ topology['VMs'] | length > 0 }}" + when: "{{ topo_vms | length > 0 }}" supervisorctl: name: "exabgpv4:" state: present # present contains `supervisorctl reread` and `supervisorctl add` @@ -59,10 +62,10 @@ - name: Verify that exabgp processes for IPv4 are started wait_for: host: "{{ ptf_host_ip }}" - port: "{{ 5000 + topology['VMs'][vm_item.key].vm_offset|int }}" + port: "{{ 5000 + topo_vms[vm_item.key].vm_offset|int }}" state: "started" timeout: 180 - loop: "{{ topology['VMs']|dict2items }}" + loop: "{{ topo_vms|dict2items }}" loop_control: loop_var: vm_item delegate_to: localhost @@ -84,7 +87,7 @@ local_asn: "{{ configuration[vm_item.key].bgp.asn }}" peer_asn: "{{ configuration[vm_item.key].bgp.asn }}" port: "{{ 6000 + vm_item.value.vm_offset|int }}" - loop: "{{ topology['VMs']|dict2items }}" + loop: "{{ topo_vms|dict2items }}" loop_control: loop_var: vm_item delegate_to: "{{ ptf_host }}" @@ -92,7 +95,7 @@ - name: Gather exabgp v6 programs set_fact: program_group_name: "exabgpv6" - program_group_programs: "{{ topology['VMs'].keys() | map('regex_replace', '^(.*)$', 'exabgp-\\1-v6') | join(',')}}" + program_group_programs: "{{ topo_vms.keys() | map('regex_replace', '^(.*)$', 'exabgp-\\1-v6') | join(',')}}" - name: Configure exabgpv6 group template: @@ -101,7 +104,7 @@ delegate_to: "{{ ptf_host }}" - name: Add exabgpv6 supervisor config and start related processes - when: "{{ topology['VMs'] | length > 0 }}" + when: "{{ topo_vms | length > 0 }}" supervisorctl: name: "exabgpv6:" state: present # present contains `supervisorctl reread` and `supervisorctl add` @@ -113,7 +116,7 @@ port: "{{ 6000 + topology.VMs[vm_item.key].vm_offset|int }}" state: "started" timeout: 180 - loop: "{{ topology['VMs']|dict2items }}" + loop: "{{ topo_vms|dict2items }}" loop_control: loop_var: vm_item delegate_to: localhost @@ -122,5 +125,6 @@ announce_routes: topo_name: "{{ topo }}" ptf_ip: "{{ ptf_host_ip }}" + dut_interfaces: "{{ dut_interfaces | default('') }}" delegate_to: localhost when: exabgp_action == 'start' diff --git a/ansible/roles/vm_set/tasks/create_dut_port.yml b/ansible/roles/vm_set/tasks/create_dut_port.yml index c2d396e2990..a7bab0ca09b 100644 --- a/ansible/roles/vm_set/tasks/create_dut_port.yml +++ b/ansible/roles/vm_set/tasks/create_dut_port.yml @@ -1,7 +1,7 @@ - name: Setup vlan port for vlan tunnel vlan_port: external_port: "{{ external_port }}" - vlan_ids: "{{ device_vlan_map_list[dut_name] }}" + vlan_ids: "{{ device_vlan_map_list[dut_name] | filter_by_dut_interfaces(dut_interfaces | default('')) }}" cmd: "create" become: yes diff --git a/ansible/roles/vm_set/tasks/get_dut_port.yml b/ansible/roles/vm_set/tasks/get_dut_port.yml index 92368060e9d..297448a9a54 100644 --- a/ansible/roles/vm_set/tasks/get_dut_port.yml +++ b/ansible/roles/vm_set/tasks/get_dut_port.yml @@ -1,7 +1,7 @@ - name: Get front panel port for vlan tunnel vlan_port: external_port: "{{ external_port }}" - vlan_ids: "{{ device_vlan_map_list[dut_name] }}" + vlan_ids: "{{ device_vlan_map_list[dut_name] | filter_by_dut_interfaces(dut_interfaces | default('')) }}" cmd: "list" become: yes when: external_port is defined diff --git a/ansible/roles/vm_set/tasks/main.yml b/ansible/roles/vm_set/tasks/main.yml index 529ef22fcf4..50aa407fde4 100644 --- a/ansible/roles/vm_set/tasks/main.yml +++ b/ansible/roles/vm_set/tasks/main.yml @@ -324,7 +324,7 @@ when: VM_num is defined and VM_num|int > 0 - name: Generate vm list of target VMs - set_fact: VM_targets={{ VM_hosts | filter_vm_targets(topology['VMs'], VM_base) | sort }} + set_fact: VM_targets={{ VM_hosts | filter_vm_targets(topology['VMs'], VM_base, dut_interfaces | default("")) | sort }} when: topology['VMs'] is defined - name: Set fallback default value for VM_targets diff --git a/ansible/roles/vm_set/tasks/remove_topo.yml b/ansible/roles/vm_set/tasks/remove_topo.yml index 46f38831e23..96168f45555 100644 --- a/ansible/roles/vm_set/tasks/remove_topo.yml +++ b/ansible/roles/vm_set/tasks/remove_topo.yml @@ -57,6 +57,7 @@ duts_mgmt_port: "{{ duts_mgmt_port }}" duts_name: "{{ duts_name.split(',') }}" max_fp_num: "{{ max_fp_num }}" + dut_interfaces: "{{ dut_interfaces | default('') }}" become: yes - name: Unbind topology {{ topo }} to DPU VMs. base vm = {{ VM_base }} diff --git a/ansible/testbed-cli.sh b/ansible/testbed-cli.sh index 448d2721ca2..8a6c3fdacfb 100755 --- a/ansible/testbed-cli.sh +++ b/ansible/testbed-cli.sh @@ -80,7 +80,7 @@ function usage echo " collect-show-tech supports specify output path for dumped files" echo " -e output_path=" echo - echo "You should define your testbed in testbed CSV file" + echo "You should define your topology in testbed YAML file" echo exit } @@ -142,7 +142,7 @@ function read_yaml tb_line=${tb_lines[0]} line_arr=($1) - for attr in group-name topo ptf_image_name ptf ptf_ip ptf_ipv6 ptf_extra_mgmt_ip netns_mgmt_ip server vm_base dut inv_name auto_recover comment; + for attr in group-name topo ptf_image_name ptf ptf_ip ptf_ipv6 ptf_extra_mgmt_ip netns_mgmt_ip server vm_base dut inv_name auto_recover comment servers; do value=$(python -c "from __future__ import print_function; tb=eval(\"$tb_line\"); print(tb.get('$attr', None))") [ "$value" == "None" ] && value= @@ -165,6 +165,7 @@ function read_yaml dut=${line_arr[11]} duts=$(python -c "from __future__ import print_function; print(','.join(eval(\"$dut\")))") inv_name=${line_arr[12]} + servers=${line_arr[15]} # Remove the dpu duts by the keyword 'dpu' in the dut name duts=$(echo $duts | sed "s/,[^,]*dpu[^,]*//g") } @@ -249,6 +250,38 @@ function stop_topo_vms -e VM_base="$vm_base" -e vm_type="$vm_type" -e topo="$topo" $@ } +function parse_servers +{ + index=$1 + servers=$2 + + echo "Parsing parameters for server $index" + + _line_arr=() + for attr in ptf ptf_ip ptf_ipv6 ptf_extra_mgmt_ip netns_mgmt_ip vm_base dut_interfaces; + do + value=$(python -c "from __future__ import print_function; tb=eval(\"list($servers.values())[$index]\"); print(tb.get('$attr', None))") + [ "$value" == "None" ] && value= + _line_arr=("${_line_arr[@]}" "$value") + done + + ptf=${_line_arr[0]} + ptf_ip=${_line_arr[1]} + ptf_ipv6=${_line_arr[2]} + ptf_extra_mgmt_ip=${_line_arr[3]} + if [ ! -z "$ptf_extra_mgmt_ip" ]; then + ptf_extra_mgmt_ip=$(python -c "from __future__ import print_function; print(','.join(eval(\"$ptf_extra_mgmt_ip\")))") + fi + netns_mgmt_ip=${_line_arr[4]} + server=$(python -c "from __future__ import print_function; tb=eval(\"list($servers.keys())[$index]\"); print(tb)") + vm_base=${_line_arr[5]} + dut_interfaces=${_line_arr[6]} + if [[ $dut_interfaces == *" "* ]]; then + echo "please delete spaces in the dut_interfaces" + exit -1 + fi +} + function add_topo { testbed_name=$1 @@ -269,19 +302,38 @@ function add_topo ansible_options+=" -e eos_batch_size=1" fi - ANSIBLE_SCP_IF_SSH=y ansible-playbook -i $vmfile -i ${inv_name} testbed_add_vm_topology.yml --vault-password-file="${passwd}" -l "$server" \ - -e testbed_name="$testbed_name" -e duts_name="$duts" -e VM_base="$vm_base" \ - -e ptf_ip="$ptf_ip" -e topo="$topo" -e vm_set_name="$vm_set_name" \ - -e ptf_imagename="$ptf_imagename" -e vm_type="$vm_type" -e ptf_ipv6="$ptf_ipv6" \ - -e ptf_extra_mgmt_ip="$ptf_extra_mgmt_ip" -e netns_mgmt_ip="$netns_mgmt_ip" \ - $ansible_options $@ - - if [[ "$ptf_imagename" != "docker-keysight-api-server" ]]; then - ansible-playbook fanout_connect.yml -i $vmfile --limit "$server" --vault-password-file="${passwd}" -e "dut=$duts" $@ + server_count=1 + if [ -n "$servers" ]; then + server_count=$(python -c "from __future__ import print_function; print(len(eval(\"$servers\")))") fi - # Delete the obsoleted arp entry for the PTF IP - ip neighbor flush $ptf_ip || true + for i in $(seq 0 $(($server_count-1))) + do + if [ -n "$servers" ]; then + parse_servers "$i" "$servers" + ansible_options+=" -e dut_interfaces=$dut_interfaces" + fi + + ANSIBLE_SCP_IF_SSH=y ansible-playbook -i $vmfile -i ${inv_name} testbed_add_vm_topology.yml --vault-password-file="${passwd}" -l "$server" \ + -e testbed_name="$testbed_name" -e duts_name="$duts" -e VM_base="$vm_base" \ + -e ptf_ip="$ptf_ip" -e topo="$topo" -e vm_set_name="$vm_set_name" \ + -e ptf_imagename="$ptf_imagename" -e vm_type="$vm_type" -e ptf_ipv6="$ptf_ipv6" \ + -e ptf_extra_mgmt_ip="$ptf_extra_mgmt_ip" -e netns_mgmt_ip="$netns_mgmt_ip" \ + $ansible_options $@ + + if [ $i -eq 0 ]; then + fanout_options+=" -e clean_before_add=y" + else + fanout_options+=" -e clean_before_add=n" + fi + + if [[ "$ptf_imagename" != "docker-keysight-api-server" ]]; then + ansible-playbook fanout_connect.yml -i $vmfile --limit "$server" --vault-password-file="${passwd}" -e "dut=$duts" $fanout_options $@ + fi + + # Delete the obsoleted arp entry for the PTF IP + ip neighbor flush $ptf_ip || true + done cache_files_path_value=$(is_cache_exist) if [[ -n $cache_files_path_value ]]; then @@ -314,6 +366,18 @@ function remove_topo ansible_options="-e sonic_vm_storage_location=$sonic_vm_dir" fi + server_count=1 + if [ -n "$servers" ]; then + server_count=$(python -c "from __future__ import print_function; print(len(eval(\"$servers\")))") + fi + + for i in $(seq 0 $(($server_count-1))) + do + if [ -n "$servers" ]; then + parse_servers "$i" "$servers" + ansible_options+=" -e dut_interfaces=$dut_interfaces" + fi + ANSIBLE_SCP_IF_SSH=y ansible-playbook -i $vmfile -i ${inv_name} testbed_remove_vm_topology.yml --vault-password-file="${passwd}" -l "$server" \ -e testbed_name="$testbed_name" -e duts_name="$duts" -e VM_base="$vm_base" \ -e ptf_ip="$ptf_ip" -e topo="$topo" -e vm_set_name="$vm_set_name" \ @@ -322,6 +386,8 @@ function remove_topo -e remove_keysight_api_server="$remove_keysight_api_server" \ $ansible_options $@ + done + echo Done } diff --git a/ansible/testbed.yaml b/ansible/testbed.yaml index d318e0f6f05..ce9d6db2571 100644 --- a/ansible/testbed.yaml +++ b/ansible/testbed.yaml @@ -243,3 +243,26 @@ inv_name: snappi-sonic auto_recover: 'True' comment: Batman + +- conf-name: vms-multi-servers-testbed-demo + group-name: vms1-3 + topo: t0 + ptf_image_name: docker-ptf + servers: + server_1: + ptf: ptf_mst1 + ptf_ip: 10.255.0.186/24 + ptf_ipv6: + vm_base: VM0100 + dut_interfaces: 0-29 + server_2: + ptf: ptf_mst2 + ptf_ip: 10.255.0.187/24 + ptf_ipv6: + vm_base: VM0300 + dut_interfaces: 30-31 + dut: + - str-msn2700-01 + inv_name: lab + auto_recover: 'True' + comment: Multi servers testbed demo diff --git a/ansible/testbed_add_vm_topology.yml b/ansible/testbed_add_vm_topology.yml index 1485d569b6b..1f0a66c5207 100644 --- a/ansible/testbed_add_vm_topology.yml +++ b/ansible/testbed_add_vm_topology.yml @@ -152,7 +152,7 @@ when: VM_base|length > 0 - name: Generate vm list of target VMs - set_fact: VM_targets={{ VM_hosts | filter_vm_targets(topology['VMs'], VM_base) }} + set_fact: VM_targets={{ VM_hosts | filter_vm_targets(topology['VMs'], VM_base, dut_interfaces | default("")) }} when: - topology['VMs'] is defined - VM_base|length > 0