Skip to content
Closed
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
129 changes: 129 additions & 0 deletions ansible/config_sonic_basedon_testbed_v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# This Playbook run time generate matching configuration file for SONiC switch(Minigraph) based on a specific testbed topology specified in testbed.csv
# When user call testbed-cli to deploy a testbed topology, use this playbook to generate matching SONiC minigraph file and deploy it into SONiC switch under test.
# Or when you know your topology name, you may use this playbook alone to generate a minigraph matching your topology name without deploy it.
#
# VM Topologies are defined inside of vars/ directory in files vars/topo_{{ topology_name}}.yml
# Every topology should have a name to distinct one topology from another on the server
# Every topology contains a ptf container which will be used as placeholder for the injected interfaces from VMs, or direct connections to PTF host
# VMs inventory file is also required to have all VMs ready for generating the minigraph file
# VMs inventory is in file 'veos'
#
# Template files for generating minigraph.xml are defined in template/topo directory
#
# To generate and deploy minigraph for SONiC switch matching the VM topology please use following command
# ansible-playbook -i lab config_sonic_basedon_testbed_v2.yml -l sonic_dut_name -e vm_base=VM0300 -e topo=t0 [-e deploy=true -e save=true]
# ansible-playbook -i lab config_sonic_basedon_testbed_v2.yml -l sonic_dut_name -e testbed_name=vms1-1 [-e deploy=true -e save=true]
#
# Parameters
# -l str-msn2700-01 - the sonic_dut_name you are going to generate minigraph for
# -e vm_base=VM0300 - the VM name which is used to as base to calculate VM name for this set
# -e topo=t0 - the name of topology to generate minigraph file
# -e testbed_name=vms1-1 - the testbed name specified in testbed.csv file
# (if you give 'testbed_name' option, will use info from testbed and ignore topo and vm_base options)
# -e deploy=True - if deploy the newly generated minigraph to the targent DUT, default is false if not defined
# -e save=True - if save the newly generated minigraph to the targent DUT as starup-config, default is false if not defined
#
# After minigraph.xml is generated, the playbook will replace the original minigraph file under ansible/minigraph/ with the newly generated minigraph file for the SONiC device.
# The playbook will based on deploy=True or False to deside if load the SONiC device with new minigraph or not.
# If deploy=true, the playbook will apply the newly generated minigraph to the SONiC switch
# If save=true, the playbook will save the newly generated minigraph to SONiC switch as startup-config
#
####################################################################################################################################################################################

- hosts: sonic
gather_facts: yes
tasks:

- block:
- name: Gathering testbed information
test_facts: testbed_name="{{ testbed_name }}"
connection: local

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

- name: set testbed_type
set_fact:
topo: "{{ testbed_facts['topo'] }}"

- name: set vm
set_fact:
vm_base: "{{ testbed_facts['vm_base'] }}"
when: "testbed_facts['vm_base'] != ''"
when: testbed_name is defined

- set_fact:
VM_topo: "{% if 'ptf' in topo %}False{% else %}True{% endif %}"
remote_dut: "{{ ansible_ssh_host }}"

- name: gather testbed VM informations
testbed_vm_info: base_vm={{ testbed_facts['vm_base'] }} topo={{ testbed_facts['topo'] }}
connection: local
when: "VM_topo | bool"

- name: find interface name mapping and indivisual interface speed if defined
port_alias: hwsku="{{ hwsku }}"
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be connection: local

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed

connection: local

- name: find all enalbed host_interfaces
set_fact:
host_if_indexes: "{{ vm_topo_config['host_interfaces'] | difference(vm_topo_config['off_host_interfaces']) }}"

- name: find all vlan interface names for T0 topology
set_fact:
vlan_intfs: "{{ vlan_intfs|default([])}} + ['{{ port_alias[item] }}' ]"
with_items: "{{ host_if_indexes }}"
when: ("'host_interfaces' in vm_topo_config") and ("'tor' in vm_topo_config['dut_type'] | lower")

- name: find all interface indexes connecting to VM
set_fact:
interface_to_vms: "{{ interface_to_vms|default({}) | combine({ item.key: item.value['interface_indexes'] }) }}"
with_dict: vm_topo_config['vm']

- name: find all interface names
set_fact:
intf_names: "{{ intf_names | default({}) | combine({item.key: port_alias[item.value[0]|int:item.value[-1]|int+1] }) }}"
with_dict: interface_to_vms

- name: save original minigraph file of ansible (ignore errors when file doesnot exist)
shell: mv minigraph/{{ inventory_hostname }}.xml minigraph/{{ inventory_hostname }}.xml.orig
connection: local
ignore_errors: true

- name: create minigraph file in ansible minigraph folder
become: true
template: src=templates/minigraph_template.j2
dest=minigraph/{{ inventory_hostname}}.xml
connection: local

- block:
- name: saved original minigraph file in SONiC DUT(ignore errors when file doesnot exist)
shell: mv /etc/sonic/minigraph.xml /etc/sonic/minigraph.xml.orig
become: true
ignore_errors: true

- name: create new minigraph file for SONiC device
template: src=templates/alltopo.j2
dest=/etc/sonic/minigraph.xml
become: true

- name: disable automatic minigraph update if we are deploying new minigraph into SONiC
lineinfile:
name: /etc/sonic/updategraph.conf
regexp: '^enabled='
line: 'enabled=false'
become: true

- name: execute cli "config load_minigraph -y" to apply new minigraph
become: true
shell: config load_minigraph -y

- name: execute cli "config bgp startup all" to bring up all bgp sessions for test
become: true
shell: config bgp startup all

- name: execute cli "config save -y" to save current minigraph as startup-config
become: true
shell: config save -y
when: save is defined and save|bool == true
when: deploy is defined and deploy|bool == true
40 changes: 26 additions & 14 deletions ansible/library/port_alias.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,28 @@
port_alias: hwsku='ACS-MSN2700'
'''

### TODO: we could eventually use sonic config package to replace this port_alias module later ###############
RETURN = '''
ansible_facts{
port_alias: [Ethernet0, Ethernet4, ....],
port_speed: ['40000', '40000', ......]
}
'''

### Here are the expectation of files of device port_config.ini located, in case changed please modify it here
FILE_PATH = '/usr/share/sonic/device'
PORTMAP_FILE = 'port_config.ini'
ALLOWED_HEADER = ['name', 'lanes', 'alias', 'index', 'speed']

class SonicPortAliasMap():
"""
Retrieve SONiC device interface port alias mapping
Retrieve SONiC device interface port alias mapping and port speed if they are definded

"""
def __init__(self, hwsku):
self.filename = ''
self.hwsku = hwsku
self.portmap = []
self.portspeed = []
return

def findfile(self):
Expand All @@ -57,21 +65,25 @@ def get_portmap(self):
raise Exception("Something wrong when trying to find the portmap file, either the hwsku is not available or file location is not correct")
with open(self.filename) as f:
lines = f.readlines()
alias=False
alias_index = 0
speed_index = 0
while len(lines) != 0:
line = lines.pop(0)
if re.match('^#', line):
title=re.sub('#', '', line.strip().lower()).split()
if 'alias' in title:
index = title.index('alias')
alias = True
else:
for text in title:
if text in ALLOWED_HEADER:
index = title.index(text)
if 'alias' in text:
alias_index = index
if 'speed' in text:
speed_index = index
else:
if re.match('^Ethernet', line):
mapping = line.split()
if alias and len(mapping) > index:
self.portmap.append(mapping[index])
else:
self.portmap.append(mapping[0])
self.portmap.append(mapping[alias_index])
if speed_index != 0:
self.portspeed.append(mapping[speed_index])
return

def main():
Expand All @@ -85,14 +97,14 @@ def main():
try:
allmap = SonicPortAliasMap(m_args['hwsku'])
allmap.get_portmap()
module.exit_json(ansible_facts={'port_alias': allmap.portmap})
module.exit_json(ansible_facts={'port_alias': allmap.portmap, 'port_speed': allmap.portspeed})
except (IOError, OSError), e:
fail_msg = "IO error" + str(e)
module.fail_json(msg=fail_msg)
except Exception, e:
fail_msg = "failed to find the correct port names for "+m_args['hwsku'] + str(e)
fail_msg = "failed to find the correct port config for "+m_args['hwsku'] + str(e)
module.fail_json(msg=fail_msg)

from ansible.module_utils.basic import *
if __name__ == "__main__":
main()
main()
58 changes: 56 additions & 2 deletions ansible/library/test_facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@
TESTBED_FILE = 'testbed.csv'
TESTCASE_FILE = 'roles/test/vars/testcases.yml'


class ParseTestbedTopoinfo():
'''
Parse the CSV file used to describe whole testbed info
Expand All @@ -103,6 +102,7 @@ class ParseTestbedTopoinfo():
def __init__(self, testbed_file):
self.testbed_filename = testbed_file
self.testbed_topo = {}
self.vm_topo_config = {}

def read_testbed_topo(self):
with open(self.testbed_filename) as f:
Expand Down Expand Up @@ -132,6 +132,58 @@ def get_testbed_info(self, testbed_name):
else:
return self.testbed_topo

def get_testbed_topo_config(self, topo_name):
if 'ptf32' in topo_name:
topo_name = 't1'
if 'ptf64' in topo_name:
topo_name = 't1-64' ###TODO: there is no t1-64 topology checked in yet
topo_filename = 'vars/topo_' + topo_name + '.yml'
vm_topo_config = dict()
### read topology definition
if not os.path.isfile(topo_filename):
raise Exception("cannot find topology definition file under vars/topo_%s.yml file!" % topo_name)
else:
with open(topo_filename) as f:
topo_definition = yaml.load(f)
### parse topo file specified in vars/ to reverse as dut config
if 'VMs' in topo_definition['topology']:
dut_asn = topo_definition['configuration_properties']['common']['dut_asn']
vm_topo_config['dut_asn'] = dut_asn
vm_topo_config['dut_type'] = topo_definition['configuration_properties']['common']['dut_type']
vmconfig = dict()
for vm in topo_definition['topology']['VMs']:
vmconfig[vm] = dict()
vmconfig[vm]['intfs'] = []
vmconfig[vm]['properties']=topo_definition['configuration'][vm]['properties']
vmconfig[vm]['interface_indexes'] = topo_definition['topology']['VMs'][vm]['vlans']
vmconfig[vm]['bgp_asn'] = topo_definition['configuration'][vm]['bgp']['asn']
for intf in topo_definition['configuration'][vm]['interfaces']:
if 'ipv4' in topo_definition['configuration'][vm]['interfaces'][intf] and ('loopback' not in intf.lower()):
(vmconfig[vm]['peer_ipv4'], vmconfig[vm]['ipv4mask']) = topo_definition['configuration'][vm]['interfaces'][intf]['ipv4'].split('/')
vmconfig[vm]['ip_intf'] = intf
if 'ipv6' in topo_definition['configuration'][vm]['interfaces'][intf] and ('loopback' not in intf.lower()):
(ipv6_addr, vmconfig[vm]['ipv6mask']) = topo_definition['configuration'][vm]['interfaces'][intf]['ipv6'].split('/')
vmconfig[vm]['ip_intf'] = intf
vmconfig[vm]['peer_ipv6'] = ipv6_addr.upper()
if 'Ethernet' in intf:
vmconfig[vm]['intfs'].append(intf)
for ip in topo_definition['configuration'][vm]['bgp']['peers'][dut_asn]:
if ip[0:5].upper() in vmconfig[vm]['peer_ipv4'].upper():
vmconfig[vm]['bgp_ipv4'] = ip.upper()
if ip[0:5].upper() in vmconfig[vm]['peer_ipv6'].upper():
vmconfig[vm]['bgp_ipv6'] = ip.upper()
vm_topo_config['vm'] = vmconfig
if 'host_interfaces' in topo_definition['topology']:
vm_topo_config['host_interfaces'] = topo_definition['topology']['host_interfaces']
else:
vm_topo_config['host_interfaces'] = []
if 'off_host_interfaces' in topo_definition['topology']:
vm_topo_config['off_host_interfaces'] = topo_definition['topology']['off_host_interfaces']
else:
vm_topo_config['off_host_interfaces'] = []
self.vm_topo_config = vm_topo_config
return vm_topo_config

class TestcasesTopology():
'''
Read testcases definition yaml file under ansible/roles/test/vars/testcases.yml
Expand Down Expand Up @@ -176,7 +228,9 @@ def main():
testcaseinfo = TestcasesTopology(testcase_file)
testcaseinfo.read_testcases()
testcase_topo = testcaseinfo.get_topo_testcase()
module.exit_json(ansible_facts={'testbed_facts': testbed_topo, 'topo_testcases': testcase_topo})
if testbed_name:
vm_topo_config = topoinfo.get_testbed_topo_config(testbed_topo['topo'])
module.exit_json(ansible_facts={'testbed_facts': testbed_topo, 'topo_testcases': testcase_topo, 'vm_topo_config': vm_topo_config})
except (IOError, OSError):
module.fail_json(msg="Can not find lab testbed file "+testbed_file+" or testcase file "+testcase_file+"??")
except Exception as e:
Expand Down
76 changes: 76 additions & 0 deletions ansible/templates/minigraph_cpg.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<CpgDec>
<IsisRouters xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution"/>
<PeeringSessions>
{% for index in range(vms_number) %}
{% set vm=vms[index] %}
{% if vm_topo_config['vm'][vm]['peer_ipv4'] %}
<BGPSession>
<MacSec>false</MacSec>
<StartRouter>{{ inventory_hostname }}</StartRouter>
<StartPeer>{{ vm_topo_config['vm'][vm]['bgp_ipv4'] }}</StartPeer>
<EndRouter>{{ vm }}</EndRouter>
<EndPeer>{{ vm_topo_config['vm'][vm]['peer_ipv4'] }}</EndPeer>
<Multihop>1</Multihop>
<HoldTime>10</HoldTime>
<KeepAliveTime>3</KeepAliveTime>
</BGPSession>
{% endif %}
{% if vm_topo_config['vm'][vm]['peer_ipv6'] %}
<BGPSession>
<StartRouter>{{ inventory_hostname }}</StartRouter>
<StartPeer>{{ vm_topo_config['vm'][vm]['bgp_ipv6'] }}</StartPeer>
<EndRouter>{{ vm }}</EndRouter>
<EndPeer>{{ vm_topo_config['vm'][vm]['peer_ipv6'] }}</EndPeer>
<Multihop>1</Multihop>
<HoldTime>10</HoldTime>
<KeepAliveTime>3</KeepAliveTime>
</BGPSession>
{% endif %}
{% endfor %}
</PeeringSessions>
<Routers xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution">
<a:BGPRouterDeclaration>
<a:ASN>{{ vm_topo_config['dut_asn'] }}</a:ASN>
<a:Hostname>{{ inventory_hostname }}</a:Hostname>
<a:Peers>
{% for index in range(vms_number) %}
<BGPPeer>
<Address>{{ vm_topo_config['vm'][vms[index]]['peer_ipv4'] }}</Address>
<RouteMapIn i:nil="true"/>
<RouteMapOut i:nil="true"/>
<Vrf i:nil="true"/>
</BGPPeer>
{% endfor %}
{% if 'tor' in vm_topo_config['dut_type'] | lower %}
<BGPPeer i:type="a:BGPPeerPassive">
<ElementType>BGPPeer</ElementType>
<Address>10.1.0.32</Address>
<RouteMapIn i:nil="true"/>
<RouteMapOut i:nil="true"/>
<Vrf i:nil="true"/>
<a:Name>BGPSLBPassive</a:Name>
<a:PeersRange>10.255.0.0/25</a:PeersRange>
</BGPPeer>
<BGPPeer i:type="a:BGPPeerPassive">
<ElementType>BGPPeer</ElementType>
<Address>10.1.0.32</Address>
<RouteMapIn i:nil="true"/>
<RouteMapOut i:nil="true"/>
<Vrf i:nil="true"/>
<a:Name>BGPVac</a:Name>
<a:PeersRange>192.168.0.0/25</a:PeersRange>
</BGPPeer>
{% endif %}
</a:Peers>
<a:RouteMaps/>
</a:BGPRouterDeclaration>
{% for index in range( vms_number) %}
<a:BGPRouterDeclaration>
<a:ASN>{{ vm_topo_config['vm'][vms[index]]['bgp_asn'] }}</a:ASN>
<a:Hostname>{{ vms[index] }}</a:Hostname>
<a:RouteMaps/>
</a:BGPRouterDeclaration>
{% endfor %}
</Routers>
</CpgDec>

Loading