Skip to content

Commit

Permalink
gen-mg changes to generate VoQ fabric data, Linecard System ports, In…
Browse files Browse the repository at this point in the history
…band, iBGP-peers (#3245)

1. Support chassis, multi-duts scenarios in TestbedProcessing.
When testbed.yaml file contains card_type=Linecard or card_type=supervisor,
TestbedProcessing will add that to lab, veos inventory files.
When dut is multi-dut, TestbedProcessing will convert the dut type list to string.
makeMain needs to publish supported_vm_types=[...] into main.yml
2. Support fabric_info generation in fabric_info.py when the num_asic > 0 and card_type is supervisor.
3. Use the fabric_info in minigraph templates to generated the fabric asic info.
Also added SubRole=fabric, switch_type=fabric in DeviceMetadata for each fabric asic
4. Use variable name card_type instead of type in ansible, dut_utils.py and minigraph templates to be more explicit
5. allow t2 as a topo in testbed.py

Linecard gen-minigraph related changes:
1. support adding inband data in LC's minigraph.
VoqInbandInterfaces fields come from testbed.yaml (fields voq_inband_intf, voq_inband_type, voq_inband_ip) to lab/veos inventory files to minigraph.
Data flows from testbed.yaml (fields voq_inband_intf, voq_inband_type, voq_inband_ip) to lab/veos inventory files to minigraph.

2. System ports

Data flows from
a. port_config.ini (new fileds required are: numVoq, coreId, corePortId), (existing fields: name, speed)
b. switchId = running asic_id count across all linecards in the chassis
c. systemPortId = running systemport count across all linecards in the chassis
Each dut adds its own system-ports to its own ansible_facts.
config_sonic_basedon_testbed.yml loops through all duts system-ports to create all_sysports
d. added changes to port_alias to generate system ports for Recirc ports as well
e. hostname comes from config_sonic_basedon_testbed.yml, asicname created in port_alias from asic_id when looping through num_asics

3. DeviceProperty
   <a:DeviceProperty>
     <a:Name>SwitchType</a:Name>
     <a:Reference i:nil="true"/>
     <a:Value>voq</a:Value>
   </a:DeviceProperty>
   <a:DeviceProperty>
     <a:Name>MaxCores</a:Name>
     <a:Reference i:nil="true"/>
     <a:Value>16</a:Value>
   </a:DeviceProperty>
   <a:DeviceProperty>
     <a:Name>SwitchId</a:Name>
     <a:Reference i:nil="true"/>
     <a:Value>0</a:Value>
   </a:DeviceProperty>

a. switch type, maxcores are directly from testbed.yaml to inventory files to minigraph
b. start_switchid is calculated for each linecard and set into inventory files from TestbedProcessing.py based on num_asics from previous linecards

4. iBGP peers
iBGP sessions are setup between inband-ipaddress on all linecards
a. take all voq_inband_ip to form a list all_inbands in ansible/config_sonic_basedon_testbed.yml
b. minigraph_cpg iterates through all_inbands and configures a BGPSession, BGPPeer between
the voq_inband_ip and all other addresses in all_inbands
  • Loading branch information
saravanansv authored Jun 21, 2021
1 parent 9d0cfc8 commit e6741d3
Show file tree
Hide file tree
Showing 13 changed files with 513 additions and 26 deletions.
136 changes: 125 additions & 11 deletions ansible/TestbedProcessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def makeMain(data, outfile):
}
}
with open(outfile, "w") as toWrite:
toWrite.write( "supported_vm_types: [ 'veos', 'ceos', 'vsonic' ]\n" ),
yaml.dump(dictData, stream=toWrite, default_flow_style=False)
toWrite.write("# proxy\n")
yaml.dump(proxy, stream=toWrite, default_flow_style=False)
Expand Down Expand Up @@ -164,7 +165,7 @@ def makeVMHostCreds(data, outfile):
error handling: checks if attribute values are None type or string "None"
"""
def makeSonicLabDevices(data, outfile):
csv_columns = "Hostname,ManagementIp,HwSku,Type"
csv_columns = "Hostname,ManagementIp,HwSku,Type,CardType"
topology = data
csv_file = outfile

Expand All @@ -175,17 +176,19 @@ def makeSonicLabDevices(data, outfile):
hostname = device
managementIP = str(deviceDetails.get("ansible").get("ansible_host"))
hwsku = deviceDetails.get("hwsku")
devType = deviceDetails.get("device_type")

devType = deviceDetails.get("device_type") #DevSonic, server, FanoutRoot etc
cardType = deviceDetails.get("card_type") #supervisor, Linecard etc
# catch empty values
if not managementIP:
managementIP = ""
if not hwsku:
hwsku = ""
if not devType:
devType = ""
if not cardType:
cardType = ""

row = hostname + "," + managementIP + "," + hwsku + "," + devType
row = hostname + "," + managementIP + "," + hwsku + "," + devType + "," + cardType
f.write(row + "\n")
except IOError:
print("I/O error: makeSonicLabDevices")
Expand Down Expand Up @@ -240,6 +243,11 @@ def makeTestbed(data, outfile):
ptf = ""
if not comment:
comment = ""
# dut is a list for multi-dut testbed, convert it to string
if type(dut) is not str:
dut = dut.__str__()
dut = dut.replace(",", ";")
dut = dut.replace(" ", "")

row = confName + "," + groupName + "," + topo + "," + ptf_image_name + "," + ptf + "," + ptf_ip + "," + ptf_ipv6 + ","+ server + "," + vm_base + "," + dut + "," + comment
f.write(row + "\n")
Expand All @@ -265,6 +273,8 @@ def makeSonicLabLinks(data, outfile):
for key, item in topology.items():
startDevice = key
interfacesDetails = item.get("interfaces")
if not interfacesDetails:
continue

for startPort, element in interfacesDetails.items():
startPort = startPort
Expand Down Expand Up @@ -363,6 +373,7 @@ def makeLabSecrets(data, outfile):
"""
def makeLab(data, devices, testbed, outfile):
deviceGroup = data
start_switchid = 0
with open(outfile, "w") as toWrite:
for key, value in deviceGroup.items():
#children section
Expand All @@ -377,45 +388,143 @@ def makeLab(data, devices, testbed, outfile):
toWrite.write("[" + key + "]\n")
for host in value.get("host"):
entry = host
dev = devices.get(host.lower())

if "ptf" in key:
try: #get ansible host
ansible_host = testbed.get(host).get("ansible").get("ansible_host")
ansible_host = dev.get("ansible").get("ansible_host")
entry += "\tansible_host=" + ansible_host.split("/")[0]
except:
print("\t\t" + host + ": ansible_host not found")

if ansible_host:
try: # get ansible ssh username
ansible_ssh_user = testbed.get(host.lower()).get("ansible").get("ansible_ssh_user")
ansible_ssh_user = dev.get("ansible").get("ansible_ssh_user")
entry += "\tansible_ssh_user=" + ansible_ssh_user
except:
print("\t\t" + host + ": ansible_ssh_user not found")

try: # get ansible ssh pass
ansible_ssh_pass = testbed.get(host.lower()).get("ansible").get("ansible_ssh_pass")
ansible_ssh_pass = dev.get("ansible").get("ansible_ssh_pass")
entry += "\tansible_ssh_pass=" + ansible_ssh_pass
except:
print("\t\t" + host + ": ansible_ssh_pass not found")
else: #not ptf container
try: #get ansible host
ansible_host = devices.get(host.lower()).get("ansible").get("ansible_host")
ansible_host = dev.get("ansible").get("ansible_host")
entry += "\tansible_host=" + ansible_host.split("/")[0]
except:
print("\t\t" + host + ": ansible_host not found")

if ansible_host:
try: # get ansible ssh username
ansible_ssh_user = devices.get(host.lower()).get("ansible").get("ansible_ssh_user")
ansible_ssh_user = dev.get("ansible").get("ansible_ssh_user")
entry += "\tansible_ssh_user=" + ansible_ssh_user
except:
print("\t\t" + host + ": ansible_ssh_user not found")

try: # get ansible ssh pass
ansible_ssh_pass = devices.get(host.lower()).get("ansible").get("ansible_ssh_pass")
ansible_ssh_pass = dev.get("ansible").get("ansible_ssh_pass")
entry += "\tansible_ssh_pass=" + ansible_ssh_pass
except:
print("\t\t" + host + ": ansible_ssh_pass not found")
try: #get hwsku
hwsku = dev.get("hwsku")
if hwsku is not None:
entry += "\thwsku=" + hwsku
except AttributeError:
print("\t\t" + host + ": hwsku not found")

try: #get card_type
card_type = dev.get("card_type")
if card_type is not None:
entry += "\tcard_type=" + card_type
except AttributeError:
print("\t\t" + host + ": card_type not found")

try: #get num_fabric_asics
num_fabric_asics = dev.get("num_fabric_asics")
if num_fabric_asics is not None:
entry += "\tnum_fabric_asics=" + str( num_fabric_asics )
except AttributeError:
print("\t\t" + host + " num_fabric_asics not found")

try: #get num_asics
num_asics = dev.get("num_asics")
if num_asics is not None:
entry += "\tnum_asics=" + str( num_asics )
except AttributeError:
print("\t\t" + host + " num_asics not found")

if card_type != 'supervisor':
entry += "\tstart_switchid=" + str( start_switchid )
if num_asic is not None:
start_switchid += int( num_asic )
else:
start_switchid += 1

try: #get frontend_asics
frontend_asics = dev.get("frontend_asics")
if frontend_asics is not None:
entry += "\tfrontend_asics=" + frontend_asics.__str__()
except AttributeError:
print("\t\t" + host + ": frontend_asics not found")

try: #get asics_host_ip
asics_host_ip = dev.get("asics_host_ip")
if asics_host_ip is not None:
entry += " \tasics_host_ip=" + str( asics_host_ip )
except AttributeError:
print("\t\t" + host + " asics_host_ip not found")

try: #get asics_host_ipv6
asics_host_ipv6 = dev.get("asics_host_ipv6")
if asics_host_ipv6 is not None:
entry += "\tasics_host_ipv6=" + str( asics_host_ipv6 )
except AttributeError:
print("\t\t" + host + " asics_host_ipv6 not found")

try: #get voq_inband_ip
voq_inband_ip = dev.get("voq_inband_ip")
if voq_inband_ip is not None:
entry += "\tvoq_inband_ip=" + str( voq_inband_ip )
except AttributeError:
print("\t\t" + host + " voq_inband_ip not found")

try: #get voq_inband_ipv6
voq_inband_ipv6 = dev.get("voq_inband_ipv6")
if voq_inband_ipv6 is not None:
entry += "\tvoq_inband_ipv6=" + str( voq_inband_ipv6 )
except AttributeError:
print("\t\t" + host + " voq_inband_ipv6 not found")

try: #get voq_inband_intf
voq_inband_intf = dev.get("voq_inband_intf")
if voq_inband_intf is not None:
entry += "\tvoq_inband_intf=" + str( voq_inband_intf )
except AttributeError:
print("\t\t" + host + " voq_inband_intf not found")

try: #get voq_inband_type
voq_inband_type = dev.get("voq_inband_type")
if voq_inband_type is not None:
entry += "\tvoq_inband_type=" + str( voq_inband_type )
except AttributeError:
print("\t\t" + host + " voq_inband_type not found")

try: #get switch_type
switch_type = dev.get("switch_type")
if switch_type is not None:
entry += "\tswitch_type=" + str( switch_type )
except AttributeError:
print("\t\t" + host + " switch_type not found")

try: #get max_cores
max_cores = dev.get("max_cores")
if max_cores is not None:
entry += "\tmax_cores=" + str( max_cores )
except AttributeError:
print("\t\t" + host + " max_cores not found")

toWrite.write(entry + "\n")
toWrite.write("\n")
Expand Down Expand Up @@ -455,8 +564,13 @@ def makeVeos(data, veos, devices, outfile):
entry = host

try:
ansible_host = devices.get(host.lower()).get("ansible").get("ansible_host")
dev = devices.get(host.lower())
ansible_host = dev.get("ansible").get("ansible_host")
entry += "\tansible_host=" + ansible_host.split("/")[0]
if dev.get("device_type") == "DevSonic":
entry += "\ttype=" + dev.get("type")
entry += "\thwsku=" + dev.get("hwsku")
entry += "\tcard_type=" + dev.get("card_type")
except:
try:
ansible_host = veos.get(key).get(host).get("ansible_host")
Expand Down
32 changes: 30 additions & 2 deletions ansible/config_sonic_basedon_testbed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,42 @@
ignore_errors: true

- name: find interface name mapping and individual interface speed if defined from dut
port_alias: hwsku="{{ hwsku }}"
port_alias:
hwsku: "{{ hwsku }}"
card_type: "{{ card_type | default('fixed') }}"
hostname: "{{ inventory_hostname | default('') }}"
start_switchid: "{{ start_switchid | default(0) }}"
when: deploy is defined and deploy|bool == true

- name: find interface name mapping and individual interface speed if defined with local data
port_alias: hwsku="{{ hwsku }}" num_asic="{{ num_asics }}"
port_alias:
hwsku: "{{ hwsku }}"
num_asic: "{{ num_asics }}"
card_type: "{{ card_type | default('fixed') }}"
hostname: "{{ inventory_hostname | default('') }}"
start_switchid: "{{ start_switchid | default(0) }}"
delegate_to: localhost
when: deploy is not defined or deploy|bool == false

- name: find and generate fabric ASIC infomation
fabric_info:
num_fabric_asic: "{{ num_fabric_asics | default(0) }}"
asics_host_basepfx: "{{ asics_host_ip | default(None) }}"
asics_host_basepfx6: "{{ asics_host_ipv6 | default(None) }}"

- name: set all VoQ system ports information
set_fact:
all_sysports: "{{ all_sysports | default( [] ) + hostvars[item]['sysports'] }}"
when: hostvars[item]['sysports'] is defined
loop: "{{ ansible_play_batch }}"

- name: set all VoQ information for iBGP
set_fact:
all_inbands: "{{ all_inbands | default( [] ) + [ hostvars[item]['voq_inband_ip'] ] }}"
all_hostnames: "{{ all_hostnames | default( [] ) + [ item ] }}"
when: hostvars[item]['voq_inband_ip'] is defined
loop: "{{ ansible_play_batch }}"

- name: find all enabled host_interfaces
set_fact:
host_if_indexes: "{{ vm_topo_config['host_interfaces_by_dut'][dut_index|int] | difference(vm_topo_config['disabled_host_interfaces_by_dut'][dut_index|int]) }}"
Expand Down
4 changes: 4 additions & 0 deletions ansible/library/conn_graph_facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,12 @@ def parse_graph(self):
deviceinfo[hostname] = {}
hwsku = dev.attrib['HwSku']
devtype = dev.attrib['Type']
card_type = "Linecard"
if 'CardType' in dev.attrib:
card_type = dev.attrib['CardType']
deviceinfo[hostname]['HwSku'] = hwsku
deviceinfo[hostname]['Type'] = devtype
deviceinfo[hostname]['CardType'] = card_type
self.links[hostname] = {}
devicel2info = {}
devicel3s = self.root.find(self.dpgtag).findall('DevicesL3Info')
Expand Down
71 changes: 71 additions & 0 deletions ansible/library/fabric_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env python

import ipaddress

This comment has been minimized.

Copy link
@abdosi

abdosi Jul 21, 2021

Contributor

@saravanansv why we need this ? Can you please clarify this.

This comment has been minimized.

Copy link
@saravanansv

saravanansv Jul 21, 2021

Author Contributor

I have tagged you to the original comment from @wangxin on why ipaddress is used.
Basically to calculate each fabric docker's ip_prefix from asics_host_basepfx="10.1.0.1/32"

This comment has been minimized.

Copy link
@sanmalho-git

sanmalho-git Jul 22, 2021

Contributor

@saravanansv can you please forward/tag me as well to the comment from xin

This comment has been minimized.

Copy link
@sanmalho-git

sanmalho-git Jul 22, 2021

Contributor

@saravanansv The question is why is an ip/ipv6 prefix needed in the first place for a fabric asic. I don't see any code in minigraph.py in sonic-buildimage that is parsing this info to put something in config_db.json

This comment has been minimized.

Copy link
@saravanansv

saravanansv Jul 22, 2021

Author Contributor

fabric_info['ip_prefix'] is used in minigraph_dpg.j2 to set the loopback intf addrs.
If its not done, then there is a chance for multiple fabric dockers to have same loopback addrs.

DOCUMENTATION = '''
module: fabric_info.py
short_description: Find SONiC Fabric ASIC inforamtion if applicable for the DUT
Description:
When the testbed has Fabric ASICs, this module helps to collect that information
which helps in generating the minigraph
Input:
num_fabric_asic asics_host_basepfx asics_host_basepfx6
Return Ansible_facts:
fabric_info: SONiC Fabric ASIC information
'''

EXAMPLES = '''
- name: get Fabric ASIC info
fabric_info: num_fabric_asic=1 asics_host_basepfx="10.1.0.1/32" asics_host_basepfx="FC00:1::1/128"
'''

RETURN = '''
ansible_facts{
fabric_info: [{'asicname': 'ASIC0', 'ip_prefix': '10.1.0.1/32', 'ip6_prefix': 'FC00:1::1/128'},
{'asicname': 'ASIC1', 'ip_prefix': '10.1.0.2/32', 'ip6_prefix': 'FC00:1::2/128'}]
}
'''

def main():
module = AnsibleModule(
argument_spec=dict(
num_fabric_asic=dict(type='str', required=True),
asics_host_basepfx=dict(type='str', required=False),
asics_host_basepfx6=dict(type='str', required=False)
),
supports_check_mode=True
)
m_args = module.params
try:
fabric_info = []
# num_fabric_asic may not be present for fixed systems which have no Fabric ASIC.
# Then return empty fabric_info
if 'num_fabric_asic' not in m_args or int(m_args['num_fabric_asic']) < 1:
module.exit_json(ansible_facts={'fabric_info': fabric_info})
return
num_fabric_asic = int( m_args[ 'num_fabric_asic' ] )
v4pfx = str( m_args[ 'asics_host_basepfx' ] ).split("/")
v6pfx = str( m_args[ 'asics_host_basepfx6' ] ).split("/")
v4base = int( ipaddress.IPv4Address(v4pfx[0]) )
v6base = int( ipaddress.IPv6Address(v6pfx[0]) )
for asic_id in range(num_fabric_asic):
key = "ASIC%d" % asic_id
next_v4addr = str( ipaddress.IPv4Address(v4base + asic_id) )
next_v6addr = str( ipaddress.IPv6Address(v6base + asic_id) )
data = { 'asicname': key,
'ip_prefix': next_v4addr + "/" + v4pfx[-1],
'ip6_prefix': next_v6addr + "/" + v6pfx[-1] }
fabric_info.append( data )
module.exit_json(ansible_facts={'fabric_info': fabric_info})
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 fabric asic info " + str(e)
module.fail_json(msg=fail_msg)

from ansible.module_utils.basic import *
if __name__ == "__main__":
main()
Loading

0 comments on commit e6741d3

Please sign in to comment.