configure_switchport.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. #!/usr/bin/env python2
  2. import argparse
  3. import sys
  4. import re
  5. import subprocess
  6. import os
  7. import csv
  8. import json
  9. def main():
  10. parser = argparse.ArgumentParser(
  11. prog=sys.argv[0], description='Configure a switch port')
  12. parser.add_argument('--switch', '-s', metavar='<SWITCH NAME>',
  13. help='Name of the switch to configure', required=True)
  14. parser.add_argument('--interface', '-i', action='append', metavar='<INTF>',
  15. help='Name of interface to configure (can be specified multiple times)')
  16. parser.add_argument('--descr', '-d', metavar='<INTF_DESCR>',
  17. help='Interface description')
  18. parser.add_argument('--mode', '-m', metavar='<trunk|access>',
  19. help='Switchport mode to configure (access or trunk)')
  20. parser.add_argument('--mtu', metavar='<MTU>',
  21. help='MTU for switchport', type=int)
  22. parser.add_argument('--port-channel', metavar='<PC NUM>',
  23. help='Port-channel number (also used as for vPC)', type=int)
  24. parser.add_argument(
  25. '--no-vpc', help='Whether or not to put port-channel in a vPC (default: yes)', action='store_true')
  26. parser.add_argument('--access-vlan', '-a', metavar='<ACCESS_VLAN_NUM>',
  27. help='Access VLAN number (when mode=access)')
  28. parser.add_argument('--trunk-allowed-vlans', metavar='<TRUNK_VLANS>',
  29. help='List of VLANs allowed on the trunk (when mode=trunk)')
  30. parser.add_argument('--pc-descr', metavar='<PORT_CHANNEL_DESCR>',
  31. help='Description for the port-channel interface (default is to use the interface description)')
  32. parser.add_argument('--username', '-u', metavar='<USERNAME>',
  33. help='Username to use to connect to the N9Ks', required=True)
  34. parser.add_argument('--input', metavar='<input TSV file>',
  35. help='Path to the input TSV file')
  36. args = parser.parse_args()
  37. n9k_switchports = []
  38. if args.input:
  39. with open(args.input, 'rb') as tsvin:
  40. tsvin = csv.reader(tsvin, delimiter='\t')
  41. for row in tsvin:
  42. if len(row) >= 4:
  43. row[3] = re.sub(r'\s', '', row[3])
  44. n9k_switchport = {
  45. 'name': row[1].capitalize().strip(),
  46. 'descr': row[0].strip(),
  47. 'mode': row[2].strip()
  48. }
  49. m = re.match(r'Ethernet\d+/\d+(/\d+)?',
  50. n9k_switchport['name'])
  51. if not m:
  52. print('WARNING: Invalid interface name {}'.format(
  53. n9k_switchport['name']))
  54. continue
  55. if row[2] == 'access':
  56. n9k_switchport['access_vlan'] = row[3]
  57. elif row[2] == 'trunk':
  58. n9k_switchport['trunk_allowed_vlans'] = row[3]
  59. else:
  60. print(
  61. 'WARNING: Invalid value for mode, {}'.format(row[2]))
  62. continue
  63. if len(row) >= 5:
  64. if m.group(1) is None:
  65. mtu = 1500
  66. try:
  67. mtu = int(row[4])
  68. except:
  69. print('WARNING: MTU must be an integer for {}'.format(
  70. n9k_switchport['name']))
  71. continue
  72. if mtu < 1500 or mtu > 9216:
  73. print('WARNING: MTU for {} must be between 1500 and 9216'.format(
  74. n9k_switchport['name']))
  75. continue
  76. n9k_switchport['mtu'] = mtu
  77. if len(row) >= 6:
  78. pcn = None
  79. try:
  80. pcn = int(row[5])
  81. except:
  82. print(
  83. 'WARNING: Port-channel must be an integer for {}'.format(n9k_switchport['name']))
  84. continue
  85. if pcn < 1 or pcn > 4096:
  86. print(
  87. 'WARNING: Port-channel number for {} must be between 1 and 4096'.format(n9k_switchport['name']))
  88. continue
  89. n9k_switchport['port_channel'] = pcn
  90. if len(row) >= 7:
  91. if re.match(r'[tT]rue', row[6]):
  92. n9k_switchport['vpc'] = True
  93. if len(row) >= 8:
  94. n9k_switchport['pc_descr'] = row[7]
  95. n9k_switchports.append(n9k_switchport)
  96. else:
  97. if not args.mode or (args.mode != 'trunk' and args.mode != 'access'):
  98. print('ERROR: Mode must be one of "trunk" or "access"')
  99. sys.exit(1)
  100. if not args.interface or len(args.interface) == 0:
  101. print('ERROR: At least one interface must be specified')
  102. sys.exit(1)
  103. if args.mode != 'access' and args.access_vlan:
  104. print('ERROR: Access VLAN must only be specified when mode is access')
  105. sys.exit(1)
  106. if args.mode != 'trunk' and args.trunk_allowed_vlans:
  107. print('ERROR: Trunk allowed VLANs must only be specified when mode is trunk')
  108. sys.exit(1)
  109. if args.mode == 'access' and not args.access_vlan:
  110. print('ERROR: You must specify an access VLAN when mode is access')
  111. sys.exit(1)
  112. if args.mode == 'trunk' and not args.trunk_allowed_vlans:
  113. args.trunk_allowed_vlans = '1-4094'
  114. if args.port_channel and (args.port_channel < 1 or args.port_channel > 4096):
  115. print('ERROR: Port-channel number must be between 1 and 4096')
  116. sys.exit(1)
  117. for intf in args.interface:
  118. m = re.match(r'[eE]thernet\d+/\d+(/\d+)?', intf)
  119. if not m:
  120. print(
  121. 'WARNING: The interface {} is not in the format "Ethernet[FEX/]MOD/PORT (e.g., Ethernet101/1/2)"'.format(intf))
  122. continue
  123. n9k_switchport = {
  124. 'name': intf.capitalize(),
  125. 'mode': args.mode,
  126. }
  127. if args.descr:
  128. n9k_switchport['descr'] = args.descr
  129. if args.mode == 'access':
  130. n9k_switchport['access_vlan'] = args.access_vlan
  131. else:
  132. n9k_switchport['trunk_allowed_vlans'] = args.trunk_allowed_vlans
  133. if m.group(1) is None:
  134. if args.mtu:
  135. if args.mtu < 1500 or args.mtu > 9216:
  136. print('WARNING: MTU must be between 1500 and 9216')
  137. continue
  138. n9k_switchport['mtu'] = args.mtu
  139. if args.port_channel:
  140. n9k_switchport['port_channel'] = args.port_channel
  141. if not args.no_vpc:
  142. n9k_switchport['vpc'] = True
  143. if args.pc_descr:
  144. n9k_switchport['pc_descr'] = args.pc_descr
  145. n9k_switchports.append(n9k_switchport)
  146. os.environ['ANSIBLE_FORCE_COLOR'] = 'True'
  147. os.environ['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
  148. os.environ['ANSIBLE_PERSISTENT_COMMAND_TIMEOUT'] = '300'
  149. command = ['ansible-playbook', '-i', 'inventory/hosts', '--limit', '{}'.format(args.switch),
  150. '-u', args.username, '-k', '-e',
  151. '{{"n9k_switchports": {}}}'.format(
  152. json.dumps(n9k_switchports)),
  153. '-e', 'ansible_python_interpreter={}'.format(sys.executable),
  154. 'configure-switchport-playbook.yml']
  155. p = subprocess.Popen(command, stdout=subprocess.PIPE,
  156. stderr=subprocess.STDOUT)
  157. for c in iter(lambda: p.stdout.read(1), ''):
  158. sys.stdout.write(c)
  159. sys.stdout.flush()
  160. if __name__ == '__main__':
  161. main()