-
Notifications
You must be signed in to change notification settings - Fork 1k
[minigraph] generate device under test minigraph file #207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
473ddce
7eb5c51
47a484e
b2ba867
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # 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.yml --vault-password-file=~/.password -l sonic_dut_name -e VM_base=VM0300 -e topo=t0 [-e deploy=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 deploy=True - if deploy the newly generated minigraph to the targent DUT, 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 reboot the SONiC switch after change to new minigraph to make it take effect | ||
| # | ||
| #################################################################################################################################################################################### | ||
|
|
||
| - hosts: sonic | ||
| gather_facts: yes | ||
| tasks: | ||
| - fail: msg="need to provide topology type and vm base like topo=t0 and vm_base=VM100" | ||
| when: (topo is not defined) or (vm_base is not defined) | ||
|
|
||
| - fail: msg="need hwsku, interface speed, netmask and interface prefix/postfix defined to generate configuration file" | ||
| when: (hwsku is not defined) or (iface_speed is not defined) or (mgmt_subnet_mask_length is not defined) or (iface_prefix is not defined) or (iface_postfix is not defined) | ||
|
|
||
| - set_fact: | ||
| VM_topo: "{% if 'ptf' in topo %}False{% else %}True{% endif %}" | ||
| remote_dut: "{{ ansible_ssh_host }}" | ||
| template_name: "{{ 't1' if topo=='ptf32' else topo }}" | ||
|
|
||
| - testbed_vm_info: base_vm="{{ vm_base }}" topo="{{ topo }}" | ||
| connection: local | ||
| when: VM_topo | ||
|
|
||
| - name: save original minigraph file (if original file may don't exist, then ignore errors) | ||
| shell: mv minigraph/{{ inventory_hostname }}.xml minigraph/{{ inventory_hostname }}.xml.orig | ||
| connection: local | ||
| ignore_errors: true | ||
|
|
||
| - name: create minigraph file in minigraph folder | ||
| become: true | ||
| template: src=templates/topo/{{ template_name }}.j2 | ||
| dest=minigraph/{{ inventory_hostname}}.xml | ||
| connection: local | ||
|
|
||
| - debug: var=deploy | ||
|
|
||
| - block: | ||
| - name: saved original minigraph file (if original file may don't exist, then ignore errors) | ||
| shell: mv /etc/sonic/minigraph.xml /etc/sonic/minigraph.xml.ori | ||
|
||
| become: true | ||
| ignore_errors: true | ||
|
||
| when: deploy is defined and deploy==True | ||
|
|
||
| - name: create minigraph file for SONiC device | ||
| become: true | ||
| template: src=templates/topo/{{ template_name }}.j2 | ||
| dest=/etc/sonic/minigraph.xml | ||
|
|
||
|
||
| # reload the device and wait it to come back | ||
| - name: Reboot is required for minigraph change | ||
| shell: sleep 2 && shutdown -r now "Ansible Create new configuration Minigraph file, triggered reboot." | ||
| async: 1 | ||
| poll: 0 | ||
| become: true | ||
| ignore_errors: true | ||
|
|
||
| - name: waiting for switch to come back | ||
| local_action: | ||
| wait_for host={{ remote_dut }} | ||
| port=22 | ||
| state=started | ||
| delay=30 | ||
| timeout=300 | ||
| become: false | ||
| changed_when: false | ||
| when: deploy is defined and deploy|bool == true | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,34 @@ | ||
| --- | ||
| #starlab (str) group variables | ||
| # file: group_vars/str.yml | ||
| #testlab (lab) group variables | ||
| # file: group_vars/lab.yml | ||
|
|
||
| # ntp variables | ||
| ntp_servers: ['10.0.0.1', '10.0.0.2'] | ||
|
|
||
| # syslog variables | ||
| syslog_servers: ['10.0.0.5', '10.0.0.6'] | ||
| # | ||
|
|
||
| # dns variables | ||
| dns_servers: ['10.0.0.5', '10.0.0.6'] | ||
|
|
||
| # forced_mgmt_routes | ||
| forced_mgmt_routes: ['10.0.0.100/31', '10.250.0.8', '10.255.0.0/28'] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure here: It should be '10.250.0.8' (without prefix), or '10.250.0.8/32'
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checked with current graph format, no /32 |
||
|
|
||
| # ErspanDestinationIpv4 | ||
| erspan_dest: ['10.0.0.7'] | ||
|
|
||
| radius_servers: [] | ||
|
|
||
| tacacs_servers: ['10.0.0.9', '10.0.0.8'] | ||
|
|
||
| # tacacs grous | ||
| tacacs_group: 'testlab' | ||
|
|
||
| # snmp servers | ||
| snmp_servers: ['10.0.0.9'] | ||
|
|
||
| # dhcp replay servers | ||
| dhcp_servers: ['10.0.0.1'] | ||
| # | ||
| # snmp variables | ||
| snmp_rocommunity: public | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,21 @@ | ||
| [sonic_latest] | ||
| str-msn2700-01 ansible_host=10.251.0.188 sonic_version=v2 | ||
| [sonic_mlnx_40] | ||
| str-msn2700-01 ansible_host=10.251.0.188 | ||
|
|
||
| [sonic_mlnx_40:vars] | ||
| hwsku="ACS-MSN2700" | ||
| iface_prefix="Ethernet" | ||
| iface_postfix="" | ||
|
||
| iface_speed='40000' | ||
| mgmt_subnet_mask_length="24" | ||
|
|
||
| [sonic:children] | ||
| sonic_latest | ||
| sonic_mlnx_40 | ||
|
|
||
| [ptf] | ||
| ptf_ptf1 ansible_host=10.255.0.188 ansible_ssh_user=root ansible_ssh_pass=root | ||
| ptf_vms1-1 ansible_host=10.255.0.178 ansible_ssh_user=root ansible_ssh_pass=root | ||
|
|
||
| [str:children] | ||
| [lab:children] | ||
| sonic | ||
| fanout | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| #!/usr/bin/env python | ||
|
|
||
| import re | ||
| import yaml | ||
| import os | ||
| import traceback | ||
| import subprocess | ||
| import ipaddr as ipaddress | ||
| from operator import itemgetter | ||
| from itertools import groupby | ||
| from collections import defaultdict | ||
|
|
||
| DOCUMENTATION=''' | ||
| module: testbed_vm_info.py | ||
| Ansible_version_added: 2.0.0.2 | ||
| short_description: Gather all related VMs info | ||
| Description: | ||
| When deploy testbed topology with VM connected to SONiC, gather neighbor VMs info for generating SONiC minigraph file | ||
| options: | ||
| base_vm: base vm name defined in testbed.csv for the deployed topology; required: True | ||
| topo: topology name defined in testbed.csv for the deployed topology; required: True | ||
|
|
||
| Ansible_facts: | ||
| 'neighbor_eosvm_mgmt': all VM hosts management IPs | ||
| 'topoall': topology information | ||
|
|
||
| ''' | ||
|
|
||
| EXAMPLES=''' | ||
| - name: gather vm information | ||
| testbed_vm_info: base_vm='VM0100' topo='t1' | ||
| ''' | ||
|
|
||
| ### Here are the assumption/expectation of files to gather VM informations, if the file location or name changes, please modify it here | ||
| TOPO_PATH = 'vars/' | ||
| VM_INV_FILE = 'veos' | ||
|
|
||
|
|
||
| class TestbedVMFacts(): | ||
| """ | ||
| Retrieve testbed VMs management information that for a specified toplogy defined in testbed.csv | ||
|
|
||
| """ | ||
|
|
||
| def __init__(self, toponame, vmbase): | ||
| self.topofile = TOPO_PATH+'topo_'+toponame +'.yml' | ||
| self.start_index = int(re.findall('VM(\d+)', vmbase)[0]) | ||
| self.vmhosts = {} | ||
| return | ||
|
|
||
|
|
||
| def get_neighbor_eos(self): | ||
| eos = {} | ||
| with open(self.topofile) as f: | ||
| vm_topology = yaml.load(f) | ||
| self.topoall = vm_topology | ||
| for vm in vm_topology['topology']['VMs']: | ||
| vm_index = int(vm_topology['topology']['VMs'][vm]['vm_offset'])+self.start_index | ||
| eos[vm] = vm_index | ||
| return eos | ||
|
|
||
|
|
||
| def gather_veos_vms(self): | ||
| vms = {} | ||
| with open(VM_INV_FILE) as f: | ||
| lines = f.readlines() | ||
| for line in lines: | ||
|
||
| if 'VM' in line and 'ansible_host' in line: | ||
| items = line.split() | ||
| vms[items[0]] = items[1].split('=')[1] | ||
| return vms | ||
|
|
||
| def main(): | ||
| module = AnsibleModule( | ||
| argument_spec=dict( | ||
| base_vm=dict(required=True, type='str'), | ||
| topo=dict(required=True, type='str'), | ||
| ), | ||
| supports_check_mode=False | ||
| ) | ||
| m_args = module.params | ||
| topo_type = m_args['topo'] | ||
| if 'ptf' in topo_type: | ||
| module.exit_json(ansible_facts= {'neighbor_eosvm_mgmt': {}}) | ||
| try: | ||
| vmsall = TestbedVMFacts(m_args['topo'], m_args['base_vm']) | ||
| neighbor_eos = vmsall.get_neighbor_eos() | ||
| vm_inv = vmsall.gather_veos_vms() | ||
| for eos in neighbor_eos: | ||
| vmname = 'VM'+format(neighbor_eos[eos], '04d') | ||
| if vmname in vm_inv: | ||
| vmsall.vmhosts[eos] = vm_inv[vmname] | ||
| else: | ||
| err_msg = "cannot find the vm " + vmname + " in VM inventory file, please make sure you have enough VMs for the topology you are using" | ||
| module.fail_json(msg=err_msg) | ||
| module.exit_json(ansible_facts={'neighbor_eosvm_mgmt':vmsall.vmhosts, 'topoall': vmsall.topoall}) | ||
| except (IOError, OSError): | ||
| module.fail_json(msg="Can not find file "+vmsall.topofile+" or "+VM_INV_FILE) | ||
| except Exception as e: | ||
| module.fail_json(msg=traceback.format_exc()) | ||
|
|
||
| from ansible.module_utils.basic import * | ||
| if __name__== "__main__": | ||
| main() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| <DeviceInfos> | ||
| <DeviceInfo> | ||
| <AutoNegotiation>true</AutoNegotiation> | ||
| <EthernetInterfaces xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution"> | ||
| {% for index in range(32) %} | ||
| {% set if_index=index*4 if ('Force10-S6000' in hwsku or 'MSN' in hwsku) else index+1 %} | ||
| <a:EthernetInterface> | ||
| <ElementType>DeviceInterface</ElementType> | ||
| <AlternateSpeeds i:nil="true"/> | ||
| <EnableAutoNegotiation>true</EnableAutoNegotiation> | ||
| <EnableFlowControl>true</EnableFlowControl> | ||
| <Index>1</Index> | ||
| {% if 'Arista-7050-QX32' in hwsku and index > 23 %} | ||
| <InterfaceName>{{ iface_prefix }}{{ if_index }}</InterfaceName> | ||
| {% else %} | ||
| <InterfaceName>{{ iface_prefix }}{{ if_index }}{{ iface_postfix }}</InterfaceName> | ||
| {% endif %} | ||
| <InterfaceType i:nil="true"/> | ||
| <MultiPortsInterface>false</MultiPortsInterface> | ||
| <PortName>0</PortName> | ||
| <Priority>0</Priority> | ||
| <Speed>{{ iface_speed }}</Speed> | ||
| </a:EthernetInterface> | ||
| {% endfor %} | ||
| </EthernetInterfaces> | ||
| <FlowControl>true</FlowControl> | ||
| <Height>0</Height> | ||
| <HwSku>{{ hwsku }}</HwSku> | ||
| <ManagementInterfaces/> | ||
| </DeviceInfo> | ||
| </DeviceInfos> | ||
| <MetadataDeclaration> | ||
| {% set syslog_servers_str=';'.join(syslog_servers) %} | ||
| {% set dhcp_servers_str=';'.join(dhcp_servers) %} | ||
| {% set forced_mgmt_routes_str = ';'.join(forced_mgmt_routes) %} | ||
| {% set ntp_servers_str = ';'.join(ntp_servers) %} | ||
| {% set snmp_servers_str = ';'.join(snmp_servers) %} | ||
| {% set tacacs_servers_str = ';'.join(tacacs_servers) %} | ||
| {% set radius_servers_str = ';'.join(radius_servers) %} | ||
| {% set erspan_dest_str = ';'.join(erspan_dest) %} | ||
| <Devices xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution"> | ||
| <a:DeviceMetadata> | ||
| <a:Name>{{ ansible_hostname }}</a:Name> | ||
| <a:Properties> | ||
| <a:DeviceProperty> | ||
| <a:Name>DhcpResources</a:Name> | ||
| <a:Reference i:nil="true"/> | ||
| <a:Value>{{ dhcp_servers_str }}</a:Value> | ||
| </a:DeviceProperty> | ||
| <a:DeviceProperty> | ||
| <a:Name>NTPResources</a:Name> | ||
| <a:Reference i:nil="true"/> | ||
| <a:Value>{{ ntp_servers_str }}</a:Value> | ||
| </a:DeviceProperty> | ||
| <a:DeviceProperty> | ||
| <a:Name>DeploymentId</a:Name> | ||
| <a:Reference i:nil="true"/> | ||
| <a:Value>1</a:Value> | ||
| </a:DeviceProperty> | ||
| <a:DeviceProperty> | ||
| <a:Name>QosProfile</a:Name> | ||
| <a:Reference i:nil="true"/> | ||
| <a:Value>Profile0</a:Value> | ||
| </a:DeviceProperty> | ||
| <a:DeviceProperty> | ||
| <a:Name>RadiusResources</a:Name> | ||
| <a:Reference i:nil="true"/> | ||
| <a:Value>{{ radius_servers_str }}</a:Value> | ||
| </a:DeviceProperty> | ||
| <a:DeviceProperty> | ||
| <a:Name>SnmpResources</a:Name> | ||
| <a:Reference i:nil="true"/> | ||
| <a:Value>{{ snmp_servers_str }}</a:Value> | ||
| </a:DeviceProperty> | ||
| <a:DeviceProperty> | ||
| <a:Name>SyslogResources</a:Name> | ||
| <a:Reference i:nil="true"/> | ||
| <a:Value>{{ syslog_servers_str }}</a:Value> | ||
| </a:DeviceProperty> | ||
| <a:DeviceProperty> | ||
| <a:Name>TacacsGroup</a:Name> | ||
| <a:Reference i:nil="true"/> | ||
| <a:Value>{{ tacacs_group }}</a:Value> | ||
| </a:DeviceProperty> | ||
| <a:DeviceProperty> | ||
| <a:Name>TacacsServer</a:Name> | ||
| <a:Reference i:nil="true"/> | ||
| <a:Value>{{ tacacs_servers_str }}</a:Value> | ||
| </a:DeviceProperty> | ||
| <a:DeviceProperty> | ||
| <a:Name>ForcedMgmtRoutes</a:Name> | ||
| <a:Reference i:nil="true"/> | ||
| <a:Value>{{ forced_mgmt_routes_str }}</a:Value> | ||
| </a:DeviceProperty> | ||
| <a:DeviceProperty> | ||
| <a:Name>ErspanDestinationIpv4</a:Name> | ||
| <a:Reference i:nil="true"/> | ||
| <a:Value>{{ erspan_dest_str }}</a:Value> | ||
| </a:DeviceProperty> | ||
| </a:Properties> | ||
| </a:DeviceMetadata> | ||
| </Devices> | ||
| <Properties xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution"/> | ||
| </MetadataDeclaration> | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VM_base -> vm_base