├── __init__.py ├── src ├── __init__.py ├── loginOS.py ├── vxlan.py ├── deviceidentity.py ├── ip_static_route.py ├── vlan.py ├── poe.py ├── common.py ├── lldp.py ├── snmp.py ├── system.py ├── tn.py ├── vsf.py └── ports.py ├── tools ├── __init__.py ├── ExcelOps.py └── maskpassw.py ├── sampledata ├── __init__.py ├── data-lldp.yaml ├── tacert.txt ├── data_static_route.yaml ├── data-vsf.yaml ├── data-tn.yaml ├── getjinja.py ├── data-vxlan.yaml ├── data-device-mgmt.yaml ├── getyaml.py └── data.yaml ├── workflows ├── __init__.py ├── vxlan.j2 ├── overlay_vxlan.py ├── ta_cert_upload.py ├── rename_ports_lldp.py ├── lldp_monitoring.py ├── vsf_info.py ├── overlay_tunneled_node.py ├── template.py ├── tn_monitoring.py ├── mac_qry.py ├── static_route.py ├── cleanup_basic_provision.py ├── device_management.py ├── basic_provision.py └── monitoring.py ├── .DS_Store ├── .gitattributes ├── requirements.txt ├── README.md ├── .gitignore ├── CONTRIBUTING.md └── License.md /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sampledata/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /workflows/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aruba/arubaos-switch-api-python/HEAD/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /sampledata/data-lldp.yaml: -------------------------------------------------------------------------------- 1 | user: admin 2 | password: admin 3 | ipadd: 4 | - 15.129.81.221 5 | -------------------------------------------------------------------------------- /sampledata/tacert.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | cert 3 | -----END CERTIFICATE----- 4 | -------------------------------------------------------------------------------- /sampledata/data_static_route.yaml: -------------------------------------------------------------------------------- 1 | user: # 2 | password: # 3 | ipadd: 4 | # - 5 | # - 6 | destination: # 7 | mask: # 8 | gateway: # -------------------------------------------------------------------------------- /sampledata/data-vsf.yaml: -------------------------------------------------------------------------------- 1 | user: # admin user 2 | password: # admin password 3 | ipadd: 4 | - #switch ip 5 | 6 | member_id: # integer value 7 | 8 | link_id: # integer value 9 | domain_id: # integer value -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi>=2022.12.07 2 | chardet==3.0.4 3 | idna==2.6 4 | pyaml==17.10.0 5 | PyYAML>=5.4 6 | requests==2.20.0 7 | urllib3>=1.25.9 8 | xlrd==1.1.0 9 | XlsxWriter==1.0.2 10 | jinja2>=2.11.3 11 | -------------------------------------------------------------------------------- /sampledata/data-tn.yaml: -------------------------------------------------------------------------------- 1 | user: # 2 | password: # 3 | ipadd: 4 | # - 5 | # - 6 | 7 | tunneled_node : {"server_configured": True, "controller_ip": "", "server_status": True, "mode": "PORT"} 8 | 9 | tunneled_ports : ['ports_list': '1,4-7'] 10 | 11 | 12 | -------------------------------------------------------------------------------- /sampledata/getjinja.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from jinja2 import Environment, FileSystemLoader 4 | import os 5 | 6 | 7 | def readjinja(file): 8 | # filename = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'sampledata', file)) 9 | env = Environment(loader=FileSystemLoader('.')) 10 | template = env.get_template(file) 11 | return template 12 | -------------------------------------------------------------------------------- /sampledata/data-vxlan.yaml: -------------------------------------------------------------------------------- 1 | user: # 2 | password: # 3 | ipadd: 4 | # - 5 | # - 6 | #example_data 7 | vxlan : [{ 'vn_id': '10', 'vn_name': 'vn-test', 'overlay_vlan': '530', 'tunnel_id':'1', 'tunnel_name': 'VXLAN_Guests', 'vxlan_source': '192.168.2.2', 'vxlan_destination': '192.168.2.3'}, 8 | { 'vn_id': '11', 'vn_name': 'vn-test1', 'overlay_vlan': '531', 'tunnel_id':'2', 'tunnel_name': 'VXLAN_Guests1', 'vxlan_source': '192.168.2.2', 'vxlan_destination': '192.168.2.4'}] 9 | -------------------------------------------------------------------------------- /tools/ExcelOps.py: -------------------------------------------------------------------------------- 1 | import xlsxwriter 2 | from xlrd import open_workbook 3 | 4 | 5 | def writetoexecl(excelname, sheetname, list): 6 | wb = xlsxwriter.Workbook(excelname) 7 | ws = wb.add_worksheet(sheetname) 8 | row = 0 9 | for col,data in enumerate(list): 10 | ws.write_column(row, col, data) 11 | wb.close() 12 | 13 | 14 | def appendtoexcel(): 15 | wb = open_workbook('MonitorPOE.xlsx') 16 | for s in wb.sheets(): 17 | print ('Sheet:',s.name) 18 | print("Last row", s.nrows) 19 | row = s.nrows+1 20 | column = s.ncols+1 21 | print("Last column",s.ncols) 22 | 23 | -------------------------------------------------------------------------------- /workflows/vxlan.j2: -------------------------------------------------------------------------------- 1 | {%- if values.jumbo %} 2 | jumbo max-frame-size {{ values.underlay_vlan }} 3 | vlan {{ values.underlay_vlan }} jumbo 4 | {%- endif %} 5 | vxlan enable 6 | {%- if values.jumbo %} 7 | vxlan udp {{ values.udp_port }} 8 | {%- endif %} 9 | virtual-network {{ values.vn_id }} {{ values.overlay_vlan }} {{ values.vn_name }} 10 | interface tunnel {{ values.tunnel_id }} 11 | {%- if values.tunnel_name %} 12 | tunnel name {{ values.tunnel_name }} 13 | {%- endif %} 14 | tunnel mode vxlan 15 | tunnel source {{ values.vxlan_source }} 16 | tunnel destination {{ values.vxlan_destination }} 17 | vxlan tunnel {{ values.tunnel_id }} overlay-vlan {{ values.overlay_vlan }} -------------------------------------------------------------------------------- /sampledata/data-device-mgmt.yaml: -------------------------------------------------------------------------------- 1 | #Credentials Template: 2 | 3 | devices : 4 | - ip_address: 5 | user: 6 | password: 7 | change_user: 8 | change_pass: 9 | past_snmp: 10 | new_snmp: 11 | - ip_address: 12 | user: 13 | password: 14 | change_user: 15 | change_pass: 16 | past_snmp: 17 | new_snmp: 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sampledata/getyaml.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | import os 3 | 4 | ''' 5 | This function is to load the yaml file used for workflows. 6 | How to use this function? 7 | 8 | By default getyaml.readyaml() will load data.yaml data. 9 | But if teh workflow reffering to a different yaml file in sampledata folder, call the function like 10 | getyaml.readyaml('data_workflowname.yaml') 11 | 12 | If the workflow requires more data try to have a different yaml file under sampledata folder. format: "data_workflowname.yaml" 13 | ''' 14 | 15 | 16 | def readyaml(filename='data.yaml'): 17 | filename = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'sampledata',filename)) 18 | with open(filename, 'r') as ymlfile: 19 | data = yaml.load(ymlfile) 20 | return data -------------------------------------------------------------------------------- /workflows/overlay_vxlan.py: -------------------------------------------------------------------------------- 1 | from requests.packages.urllib3 import disable_warnings 2 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 3 | from sampledata import getyaml 4 | from src import loginOS, vxlan 5 | 6 | disable_warnings(InsecureRequestWarning) 7 | 8 | 9 | data = getyaml.readyaml('data-vxlan.yaml') 10 | vxlan_data = data['vxlan'] 11 | for ip in (data['ipadd']): 12 | baseurl = "https://{}/rest/v4/".format(ip) 13 | try: 14 | cookie_header = loginOS.login_os(data, baseurl) 15 | for x in vxlan_data: 16 | result = vxlan.configure_vxlan(baseurl, x, cookie=cookie_header) 17 | vxlan.print_vxlandeploy(result, ip) 18 | except Exception as error: 19 | print('Ran into exception: {}. Logging out...'.format(error)) 20 | finally: 21 | loginOS.logout(baseurl, cookie_header) 22 | -------------------------------------------------------------------------------- /workflows/ta_cert_upload.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | """ 5 | Modules Import 6 | """ 7 | from requests.packages.urllib3 import disable_warnings 8 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 9 | from sampledata import getyaml 10 | from src import loginOS, system 11 | 12 | disable_warnings(InsecureRequestWarning) 13 | 14 | """ 15 | Upload TA cert to all the switches mentioned in data.yml file. 16 | """ 17 | 18 | data = getyaml.readyaml('data.yaml') 19 | for ip in data['ipadd']: 20 | baseurl = "https://{}/rest/v5/".format(ip) 21 | try: 22 | cookie_header = loginOS.login_os(data, baseurl) 23 | result = system.upload_tacert(baseurl, cookie_header, "TEST", "../sampledata/tacert.txt") 24 | print(result) 25 | 26 | except Exception as error: 27 | print('Ran into exception: {}. Logging out..'.format(error)) 28 | finally: 29 | loginOS.logout(baseurl, cookie_header) 30 | -------------------------------------------------------------------------------- /workflows/rename_ports_lldp.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | """ 5 | Modules Import 6 | """ 7 | from requests.packages.urllib3 import disable_warnings 8 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 9 | from sampledata import getyaml 10 | from src import loginOS, lldp 11 | 12 | disable_warnings(InsecureRequestWarning) 13 | 14 | """ 15 | Allows to automatically rename local port based on LLDP Remote Devices Informations 16 | """ 17 | 18 | data = getyaml.readyaml('data-lldp.yaml') 19 | for ip in data['ipadd']: 20 | baseurl = "https://{}/rest/v4/".format(ip) 21 | try: 22 | cookie_header = loginOS.login_os(data, baseurl) 23 | result = lldp.lldp(baseurl, cookie_header, info="remote") 24 | for dev in result['lldp_remote_device_element']: 25 | result = lldp.lldp_renameport(baseurl, dev, cookie_header) 26 | except Exception as error: 27 | print('Ran into exception: {}. Logging out..'.format(error)) 28 | finally: 29 | loginOS.logout(baseurl, cookie_header) 30 | -------------------------------------------------------------------------------- /src/loginOS.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | 4 | 5 | 6 | def login_os(data, url): 7 | username = data['user'] 8 | password = data['password'] 9 | params = {'userName': username, 'password': password} 10 | proxies = {'http': None, 'https': None} 11 | url_login = url + "login-sessions" 12 | response = requests.post(url_login, verify=False, data=json.dumps(params), proxies=proxies, timeout=3) 13 | if response.status_code == 201: 14 | print("Login to switch: {} is successful".format(url_login)) 15 | session = response.json() 16 | r_cookie = session['cookie'] 17 | return r_cookie 18 | else: 19 | print("Login to switch failed") 20 | 21 | 22 | def logout(url, cookie): 23 | url_login = url + "login-sessions" 24 | headers = {'cookie': cookie} 25 | proxies = {'http': None, 'https': None} 26 | r = requests.delete(url_login, headers=headers, verify=False, proxies=proxies) 27 | if r.status_code == 204: 28 | print("Logged out!", r.status_code) 29 | else: 30 | print("Logout is not successful", r.status_code) 31 | -------------------------------------------------------------------------------- /workflows/lldp_monitoring.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | """ 5 | Modules Import 6 | """ 7 | from requests.packages.urllib3 import disable_warnings 8 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 9 | from sampledata import getyaml 10 | from src import loginOS, lldp 11 | 12 | disable_warnings(InsecureRequestWarning) 13 | 14 | """ 15 | Retrieve and display LLDP Information for each devices. 16 | """ 17 | 18 | data = getyaml.readyaml('data-lldp.yaml') 19 | for ip in data['ipadd']: 20 | baseurl = "https://{}/rest/v4/".format(ip) 21 | try: 22 | cookie_header = loginOS.login_os(data, baseurl) 23 | result = lldp.lldp(baseurl, cookie_header) 24 | print("\nLLDP Global Status") 25 | lldp.print_lldpstatus(result) 26 | if result['admin_status'] == 'LLAS_ENABLED': 27 | result = lldp.lldp_remote(baseurl, cookie_header) 28 | print("\nLLDP remote devices information\n") 29 | lldp.print_lldpremote(result) 30 | except Exception as error: 31 | print('Ran into exception: {}. Logging out..'.format(error)) 32 | finally: 33 | loginOS.logout(baseurl, cookie_header) 34 | -------------------------------------------------------------------------------- /workflows/vsf_info.py: -------------------------------------------------------------------------------- 1 | from requests.packages.urllib3 import disable_warnings 2 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 3 | from sampledata import getyaml 4 | from src import loginOS 5 | from src import vsf 6 | 7 | 8 | disable_warnings(InsecureRequestWarning) 9 | 10 | data = getyaml.readyaml('data.yaml') 11 | for ip in (data['ipadd']): 12 | baseurl = "https://{}/rest/v6/".format(ip) 13 | try: 14 | cookie_header = loginOS.login_os(data, baseurl) 15 | 16 | # This workflow shows all the information that can be seen 17 | # from the "show vsf" command in CLI. 18 | print('\n\nVSF Global configuration: \n') 19 | vsf_config = vsf.get_global_config(baseurl, cookie_header) 20 | 21 | vsf_info = vsf.get_vsf_info(baseurl, cookie_header) 22 | 23 | vsf_members = vsf.get_vsf_members(baseurl, cookie_header) 24 | 25 | # Prints only the desired information not the json file. 26 | vsf.print_show_vsf(vsf_info, vsf_members, vsf_config) 27 | 28 | except Exception as error: 29 | print('Ran into exception: {}. Logging out..'.format(error)) 30 | finally: 31 | loginOS.logout(baseurl, cookie_header) 32 | -------------------------------------------------------------------------------- /tools/maskpassw.py: -------------------------------------------------------------------------------- 1 | from msvcrt import getch 2 | import getpass, sys 3 | 4 | def enterPasswd(prompt='Password: '): 5 | """ 6 | Prompt for a password and masks the input. 7 | Returns: 8 | the value entered by the user. 9 | """ 10 | 11 | if sys.stdin is not sys.__stdin__: 12 | pwd = getpass.getpass(prompt) 13 | return pwd 14 | else: 15 | pwd = "" 16 | sys.stdout.write(prompt) 17 | sys.stdout.flush() 18 | while True: 19 | key = ord(getch()) 20 | if key == 13: # Return key 21 | sys.stdout.write('\n') 22 | return pwd 23 | break 24 | if key == 8: # Backspace key 25 | if len(pwd) > 0: 26 | # Erases previous character. 27 | sys.stdout.write('\b' + ' ' + '\b') 28 | sys.stdout.flush() 29 | pwd = pwd[:-1] 30 | else: 31 | # Masks user input. 32 | char = chr(key) 33 | sys.stdout.write('*') 34 | sys.stdout.flush() 35 | pwd = pwd + char 36 | 37 | mypwd=enterPasswd() 38 | print(mypwd) -------------------------------------------------------------------------------- /workflows/overlay_tunneled_node.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | """ 5 | Modules Import 6 | """ 7 | from requests.packages.urllib3 import disable_warnings 8 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 9 | from sampledata import getyaml 10 | from src import loginOS, tn 11 | 12 | disable_warnings(InsecureRequestWarning) 13 | 14 | 15 | """ 16 | Configure Tunneled-Node feature 17 | """ 18 | 19 | 20 | data = getyaml.readyaml('data-tn.yaml') 21 | tunnel = data['tunneled_node'] 22 | tunneled_ports = data['tunneled_ports'] 23 | 24 | for ip in (data['ipadd']): 25 | baseurl = "https://{}/rest/v4/".format(ip) 26 | cookie_header = loginOS.login_os(data, baseurl) 27 | try: 28 | print("Tunneled-Node deployment") 29 | print("TN Mode : {}-Based".format(tunnel['mode'])) 30 | result = tn.tn_configuration(baseurl, tunnel, ports=tunneled_ports, cookie=cookie_header) 31 | if result == 200: 32 | print("Tunneled Node has been successfully deployed.") 33 | else: 34 | print("An error occurred during TN deployment") 35 | except Exception as error: 36 | print('Ran into exception: {}. Logging out...'.format(error)) 37 | finally: 38 | loginOS.logout(baseurl, cookie_header) 39 | -------------------------------------------------------------------------------- /workflows/template.py: -------------------------------------------------------------------------------- 1 | from requests.packages.urllib3 import disable_warnings 2 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 3 | from sampledata import getyaml 4 | from src import loginOS, vlan 5 | 6 | disable_warnings(InsecureRequestWarning) 7 | 8 | data = getyaml.readyaml() 9 | for ip in (data['ipadd']): 10 | baseurl = "https://{}/rest/v4/".format(ip) 11 | try: 12 | cookie_header = loginOS.login_os(data, baseurl) 13 | ''' Here the workflow goes! 14 | import required modules from the src folder and call the required functions 15 | You could use sampledata.yaml file to declare all your sampledata points 16 | 17 | For example below lines gets all vlans from switch and print it 18 | ''' 19 | vlans = vlan.get_vlan(baseurl, cookie_header) 20 | print('Vlans in the system are:') 21 | for i in range(len(vlans['vlan_element'])): 22 | print("Vlan ID: {} and Vlan Name: {}".format(vlans['vlan_element'][i]['vlan_id'], 23 | vlans['vlan_element'][i]['name'])) 24 | except Exception as error: 25 | print('Ran into exception: {}. Logging out..'.format(error)) 26 | finally: 27 | loginOS.logout(baseurl, cookie_header) 28 | -------------------------------------------------------------------------------- /workflows/tn_monitoring.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | """ 5 | Modules Import 6 | """ 7 | from requests.packages.urllib3 import disable_warnings 8 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 9 | from sampledata import getyaml 10 | from src import loginOS, tn 11 | 12 | disable_warnings(InsecureRequestWarning) 13 | 14 | """ 15 | Retrieve and display LLDP Informations for each devices. 16 | """ 17 | 18 | # Login to Switch 19 | data = getyaml.readyaml('data-tn.yaml') 20 | 21 | for x in data['ipadd']: 22 | baseurl = "https://{}/rest/v4/".format(x) 23 | try: 24 | cookie_header = loginOS.login_os(data, baseurl) 25 | result = tn.get_tnconf(baseurl, cookie_header) 26 | print("\nTunneled-Node Global Configuration") 27 | tn.print_tnconfig(result) 28 | if result['is_tn_server_configured'] is True: 29 | result = tn.get_tnstats(baseurl, cookie_header) 30 | print("\nTunneled-Node Statistics\n") 31 | tn.print_tnstats(result) 32 | print("\nUsers currently connected with Tunneled-Node\n") 33 | result = tn.get_tn_users(baseurl, cookie_header) 34 | print(result) 35 | except Exception as error: 36 | print('Ran into exception: {}. Logging out..'.format(error)) 37 | finally: 38 | loginOS.logout(baseurl, cookie_header) 39 | -------------------------------------------------------------------------------- /workflows/mac_qry.py: -------------------------------------------------------------------------------- 1 | from requests.packages.urllib3 import disable_warnings 2 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 3 | from src import loginOS, ports 4 | from sampledata import getyaml 5 | 6 | disable_warnings(InsecureRequestWarning) 7 | 8 | data = getyaml.readyaml() 9 | for ip in (data['ipadd']): 10 | baseurl = "https://{}/rest/v4/".format(ip) 11 | try: 12 | cookie_header = loginOS.login_os(data, baseurl) 13 | # Get all mac address from switch 14 | mac_table = ports.get_mac_table(baseurl, cookie_header) 15 | print("MAC\t\tVLAN\tPORT") 16 | print("-----------------------------") 17 | 18 | for eachitem in mac_table["mac_table_entry_element"]: 19 | for entry, edata in eachitem.items(): 20 | if entry == "mac_address": 21 | macadd = edata 22 | elif entry == "vlan_id": 23 | vlanid = edata 24 | elif entry == "port_id": 25 | portnum = edata 26 | # else: 27 | print("Mac address: {mac}\t Port id:{portnum}\t Vlan ID: {vlanid}".format(mac=macadd, vlanid=vlanid, portnum=portnum), end="") 28 | print("") 29 | 30 | except Exception as error: 31 | print('Ran into exception: {}. Logging out..'.format(error)) 32 | finally: 33 | loginOS.logout(baseurl, cookie_header) 34 | -------------------------------------------------------------------------------- /workflows/static_route.py: -------------------------------------------------------------------------------- 1 | from sampledata import getyaml 2 | from src import loginOS, ip_static_route 3 | from requests.exceptions import ConnectTimeout 4 | from requests.packages.urllib3 import disable_warnings 5 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 6 | disable_warnings(InsecureRequestWarning) 7 | 8 | data = getyaml.readyaml('data_static_route.yaml') 9 | destination = data['destination'] 10 | mask = data['mask'] 11 | gateway = data['gateway'] 12 | 13 | for x in data['ipadd']: 14 | baseurl = "https://{}/rest/v4/".format(x) 15 | try: 16 | # Login 17 | cookie_header = loginOS.login_os(data, baseurl) 18 | except ConnectTimeout as error: 19 | print('Ran into exception: {}'.format(error)) 20 | else: 21 | try: 22 | # Configure static route 23 | ip_static_route.configure_static_route(baseurl, cookie_header, destination, mask, gateway) 24 | # Get routing table 25 | static_data = ip_static_route.get_ip_route(baseurl, cookie_header) 26 | # Print RIB to screen 27 | ip_static_route.print_static_route(static_data) 28 | # Ping static gateway and print result to screen 29 | ip_static_route.gateway_check(baseurl, gateway, cookie_header) 30 | except Exception as error: 31 | print('Ran into exception: {}'.format(error)) 32 | finally: 33 | loginOS.logout(baseurl, cookie_header) 34 | -------------------------------------------------------------------------------- /sampledata/data.yaml: -------------------------------------------------------------------------------- 1 | user: admin 2 | password: admin 3 | ipadd: 4 | - 15.129.81.221 5 | # - 6 | 7 | vlan: 8 | [{"vlan_id":102,"name":"VLAN102"}, 9 | {"vlan_id":103,"name":"VLAN103"} 10 | ] 11 | 12 | port : [{ 'id': '10' , 'name': 'Phone' }, 13 | { 'id': '11' , 'name': 'Phone' }, 14 | { 'id': '12' , 'name': 'Employee' }, 15 | { 'id': '13' , 'name': 'Employee' } 16 | ] 17 | 18 | lacpport: [{"port_id": "10" , "trunk_group": "trk1"}, 19 | {"port_id": "11" , "trunk_group": "trk1"}, 20 | {"port_id": "12" , "trunk_group": "trk2"}, 21 | {"port_id": "13" , "trunk_group": "trk2"}] 22 | 23 | vlanport : [ 24 | {'vlan_id':102,'port_id':'trk1','port_mode':'POM_TAGGED_STATIC'}, 25 | {'vlan_id':103,'port_id':'trk2','port_mode':'POM_TAGGED_STATIC'} 26 | ] 27 | 28 | deviceprofile1: {"device_profile_name": "Phones", "tagged_vlan_id": 102} 29 | deviceidentity: {"device_name": "Phones", "oui": "0012bb", "is_device_profile_enabled": true, "associated_device_profile": "Phones"} 30 | 31 | ratelimitport : {"port_id": "2"} 32 | 33 | snmphosts: {"host_ip": {"version":"IAV_IP_V4","octets":"10.80.2.164"}, "community":"public", "trap_level": "STL_ALL"} 34 | 35 | 36 | linkport: {"port_id": "5", "is_enabled": false} 37 | cablediag: { "port_id": "12" } 38 | portlist: ['2','3','9'] 39 | 40 | trunkport: { "port_id": "11" , "trunk_group": "trk10" } 41 | 42 | macaddress: 2c4138-7f2705 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # arubaos-switch-python 2 | 3 | These scripts are written for ArubaOS Switch API version 4.0. 4 | 5 | ## Structure 6 | 7 | * REST API call functions are found in the /src folder. 8 | * Functions from the /src folder are combined to emulate network processes that are stored in the /workflows folder. 9 | * Data to be imported into functions is stored in the /sampledata folder. 10 | 11 | 12 | ## How to run this code 13 | There are different workflows covered in this repo under /workflows directory. Before starting ensure the switch REST API is enabled: 14 | ```switch# rest-interface``` 15 | 16 | 17 | In order to run these scripts, please complete the steps below: 18 | 1. install virtual env (refer https://docs.python.org/3/library/venv.html). Make sure python version 3 is installed in system. 19 | 20 | ``` 21 | $ python3 -m venv switchenv 22 | ``` 23 | 2. Activate the virtual env 24 | ``` 25 | $ source switchenv/bin/activate 26 | ``` 27 | 3. Install all packages required from requirements file 28 | ``` 29 | (switchenv)$ pip install -r /arubaos-switch-api-python/requirements.txt 30 | ``` 31 | 32 | 4. Open the project arubaos-switch-api-python from an editor (eg: Pycharm) 33 | 5. Set the project interpreter as the new virtual env created in step 1 34 | 6. Go to corresponding data yaml of each workflow and give the correct switch ip and credentials 35 | 7. Now you can run different workflows from arubaos-switch-api-python/workflows E.G. `base_provision.py` 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | .static_storage/ 56 | .media/ 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | .idea/ 106 | -------------------------------------------------------------------------------- /src/vxlan.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | """ 5 | Modules Import 6 | """ 7 | from sampledata import getjinja 8 | from src import common 9 | 10 | """ 11 | Functions used to configure/manage VXLAN on AOS-S 12 | """ 13 | 14 | 15 | def vxlan_status(url, cookie): 16 | """ 17 | Return VXLAN global status - "Enabled" or "Disabled" 18 | :param url: base url 19 | :param cookie: Cookie value 20 | :return: Return keyword "Enabled" or "Disabled" 21 | :Example: 22 | 23 | result = vxlan_status(base_url, sessionid) 24 | """ 25 | get_vxlan_status = common.anycli(url, "show vxlan", cookie) 26 | status = common.decoded_anycli(value=get_vxlan_status) 27 | if "Disabled" in status: 28 | return "disabled" 29 | else: 30 | return "enabled" 31 | 32 | 33 | def tunnel_status(url, cookie): 34 | """ 35 | Return VXLAN tunnels status 36 | :param url: base url 37 | :param cookie: Cookie value 38 | :return: CLI Ouput, in UTF-8 39 | :example: 40 | 41 | result = tunnel_status(base_url, sessionid) 42 | """ 43 | get_tunnel_status = common.anycli(url, "show interface tunnel type vxlan", cookie) 44 | status = common.decoded_anycli(value=get_tunnel_status) 45 | return status 46 | 47 | 48 | def configure_vxlan(url, values, cookie): 49 | """ 50 | Configure a VXLAN tunnel 51 | :param url: base url 52 | :param values: List of Informations needed for VXLAN configuration 53 | :param cookie: Cookie value 54 | :return: Request Status Code 55 | .. note: A configuration template in Jinja2 format needs to be used. 56 | :Example: 57 | 58 | result = configure_vxlan(base_url, values_list, sessionid) 59 | """ 60 | template = getjinja.readjinja('vxlan.j2') 61 | conf = template.render(values=values) 62 | result = common.batchcli(cookie, url=url, commands=conf) 63 | return result 64 | 65 | 66 | def print_vxlandeploy(value, device): 67 | """ 68 | Print VXLAN deployment status 69 | :param value: Request Status Code 70 | :param device: Targeted device 71 | :Example: 72 | 73 | result = configure_vxlan(base_url, values_list, sessionid) 74 | print_vxlandeploy(base_url, device) 75 | VXLAN has been successfully deployed on device 192.168.1.10 76 | """ 77 | if value == 202: 78 | print("VXLAN has been successfully deployed on device {}".format(device)) 79 | else: 80 | print("An error occurred") 81 | -------------------------------------------------------------------------------- /workflows/cleanup_basic_provision.py: -------------------------------------------------------------------------------- 1 | from requests.packages.urllib3 import disable_warnings 2 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 3 | from src import loginOS, vlan, common 4 | from sampledata import getyaml 5 | 6 | disable_warnings(InsecureRequestWarning) 7 | 8 | 9 | data = getyaml.readyaml() 10 | for ip in data['ipadd']: 11 | baseurl = "https://{}/rest/v4/".format(ip) 12 | vlandata = data['vlan'] 13 | try: 14 | cookie_header = loginOS.login_os(data, baseurl) 15 | # --------------- Clear Port Names --------------- # 16 | for i in range(len(data['port'])): 17 | cmd = "interface " + data['port'][i]['id'] 18 | common.anycli(baseurl, cmd, cookie_header) 19 | cmd = "no name " 20 | common.anycli(baseurl, cmd, cookie_header) 21 | # --------------- Delete trunks --------------- # 22 | for i in range(len(data['lacpport'])): 23 | cmd = "configure terminal" 24 | print(cmd) 25 | common.anycli(baseurl, cmd, cookie_header) 26 | cmd = "no trunk " + data['lacpport'][i]['port_id'] 27 | print(cmd) 28 | common.anycli(baseurl, cmd, cookie_header) 29 | # --------------- Delete Device profile and identity --------------- # 30 | cmd = "no device-profile device-type " + data['deviceprofile1']['device_profile_name'] 31 | common.anycli(baseurl, cmd, cookie_header) 32 | cmd = "no device-profile name " + data['deviceprofile1']['device_profile_name'] 33 | common.anycli(baseurl, cmd, cookie_header) 34 | cmd = "no device-identity name " + data['deviceidentity']['device_name'] 35 | common.anycli(baseurl, cmd, cookie_header) 36 | # --------------- Delete trunks --------------- # 37 | for vlans in vlandata: 38 | vlan.delete_vlans(baseurl, cookie_header, vlans['vlan_id']) 39 | # --------------- Reset ratelimits on interface --------------- # 40 | cmd = "interface 1/2" 41 | common.anycli(baseurl, cmd, cookie_header) 42 | cmd = "no rate-limit all in" 43 | common.anycli(baseurl, cmd, cookie_header) 44 | cmd = "no rate-limit all out" 45 | common.anycli(baseurl, cmd, cookie_header) 46 | except Exception as error: 47 | print('Ran into exception: {}. Logging out..'.format(error)) 48 | finally: 49 | loginOS.logout(baseurl, cookie_header) 50 | -------------------------------------------------------------------------------- /src/deviceidentity.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Dynamic OUI feature detects OUI using LLDP. 3 | 4 | https://wiki.wireshark.org/LinkLayerDiscoveryProtocol 5 | Common LLDP OUIs: 6 | 00-80-c2 - IEEE 802.1 7 | 00-12-0F - IEEE 802.3 8 | 00-12-BB - TIA TR-41 Committee - Media Endpoint Discovery (LLDP-MED, ANSI/TIA-1057) 9 | 00-0E-CF - PROFIBUS International (PNO) Extension for PROFINET discovery information 10 | 30-B2-16 - Hytec Geraetebau GmbH Extensions 11 | ''' 12 | 13 | import json 14 | import requests 15 | from src import common 16 | 17 | 18 | def create_deviceprofile(baseurl, deviceprofile1, cookie_header): 19 | """ 20 | Create Device Profile with a tagged vlan 21 | :param baseurl: imported baseurl variable 22 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 23 | :param deviceprofile1: imported data from yaml file for device profile 24 | :return: Print the status of device profile creation success/failure on the screen 25 | """ 26 | url = baseurl + 'device_profiles' 27 | headers = {'cookie': cookie_header} 28 | response = requests.post(url, verify=False, data=json.dumps(deviceprofile1), headers=headers) 29 | if response.status_code == 201: 30 | print("Device Profile: {} creation is successful" .format(deviceprofile1['device_profile_name'])) 31 | else: 32 | print("Device Profile: {} creation is not successful" .format(deviceprofile1['device_profile_name'])) 33 | 34 | cmd1 = "configure terminal" 35 | cmd2 = "device-profile name " + deviceprofile1['device_profile_name'] + " tagged-vlan " \ 36 | + str(deviceprofile1['tagged_vlan_id']) 37 | common.anycli(baseurl, cmd1, cookie_header) 38 | common.anycli(baseurl, cmd2, cookie_header) 39 | 40 | 41 | def create_deviceidentity(baseurl, deviceidentity, cookie_header): 42 | """ 43 | Create a new Device Identity 44 | :param baseurl: imported baseurl variable 45 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 46 | :param deviceidentity: imported data from yaml file for device identity creation 47 | :return: Print the status of device identity creation success/failure on the screen 48 | """ 49 | url = baseurl + 'device_identities' 50 | headers = {'cookie': cookie_header} 51 | response = requests.post(url, verify=False, data=json.dumps(deviceidentity), headers=headers) 52 | if response.status_code == 201: 53 | print("Device Identity: {} creation is successful" .format(deviceidentity['device_name'])) 54 | else: 55 | print("Device Identity: {} creation is not successful" .format(deviceidentity['device_name'])) 56 | -------------------------------------------------------------------------------- /workflows/device_management.py: -------------------------------------------------------------------------------- 1 | from requests.packages.urllib3 import disable_warnings 2 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 3 | from sampledata import getyaml 4 | from src import loginOS, snmp, system 5 | 6 | disable_warnings(InsecureRequestWarning) 7 | 8 | # Login to Switch 9 | data = getyaml.readyaml('data-device-mgmt.yaml') 10 | 11 | # Iterate through the devices in data-device-mgmt.yaml file to update User password and create new SNMP Communities 12 | for device in data['devices']: 13 | baseurl = "https://{}/rest/v4/".format(device["ip_address"]) 14 | cookie_header = loginOS.login_os(device, baseurl) 15 | 16 | try: 17 | userFound = False 18 | communityFound = False 19 | communities = snmp.get_snmp_communities(baseurl, cookie_header) 20 | print(communities['snmp_server_community_element']) 21 | for community in communities['snmp_server_community_element']: 22 | if community['community_name'] == device["past_snmp"]: 23 | communityFound = True 24 | print("Creating SNMP community string: {} in switch: {}.".format( 25 | device["past_snmp"], device["ip_address"])) 26 | result = snmp.create_snmp_community(baseurl, cookie_header, device["new_snmp"], community) 27 | if result == 201: 28 | snmp.delete_snmp_community(baseurl, cookie_header, device["past_snmp"]) 29 | if not communityFound: 30 | print("SNMP community string: {} not found in switch: {}".format(device["past_snmp"], device["ip_address"])) 31 | users = system.get_device_users(baseurl, cookie_header) 32 | for current_user in users['device_management_user_element']: 33 | if current_user['name'] == device['change_user']: 34 | userFound = True 35 | print("User {} found on {}. Updating user info".format(device['change_user'], device['ip_address'])) 36 | 37 | resp = system.update_device_user(baseurl, cookie_header, device['change_pass'], current_user) 38 | new_user = device 39 | if resp == 200: 40 | print("User update successful, Validating new password by logging in.") 41 | device1 = {"user": device['change_user'], "password": device['change_pass']} 42 | test_cookie = loginOS.login_os(device1, baseurl) 43 | loginOS.logout(baseurl, test_cookie) 44 | 45 | if not userFound: 46 | print("User {} not found on {}.".format(device["user"], device["ip_address"])) 47 | 48 | except Exception as error: 49 | print('Ran into exception: {}. Logging out..'.format(error)) 50 | 51 | # Logout from switch 52 | loginOS.logout(baseurl, cookie_header) 53 | -------------------------------------------------------------------------------- /workflows/basic_provision.py: -------------------------------------------------------------------------------- 1 | from requests.packages.urllib3 import disable_warnings 2 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 3 | from src import loginOS, vlan, deviceidentity, ports 4 | from sampledata import getyaml 5 | 6 | disable_warnings(InsecureRequestWarning) 7 | 8 | 9 | data = getyaml.readyaml() 10 | for ip in data['ipadd']: 11 | baseurl = "https://{}/rest/v4/".format(ip) 12 | try: 13 | cookie_header = loginOS.login_os(data, baseurl) 14 | # --------------- Name the Ports --------------- # 15 | portname = data['port'] 16 | print('Port Information: {}'.format(portname)) 17 | for name in portname: 18 | ports.name_ports(baseurl, cookie_header, name) 19 | # --------------- Create Vlans tagged and untagged --------------- # 20 | vlandata = data['vlan'] 21 | for vlans in vlandata: 22 | print("Vlan to create \n", vlans) 23 | vlan.create_vlan(baseurl, cookie_header, vlans) 24 | # --------------- Get all Vlans --------------- # 25 | vlans = vlan.get_vlan(baseurl, cookie_header) 26 | print('Vlans in the system are: \n') 27 | for i in range(len(vlans['vlan_element'])): 28 | print("Vlan ID: {} and Vlan Name: {}".format( 29 | vlans['vlan_element'][i]['vlan_id'], vlans['vlan_element'][i]['name'])) 30 | # --------------- create LACP ports with trunk --------------- # 31 | lacpport = data['lacpport'] 32 | for i in range(len(lacpport)): 33 | ports.create_lacp_port(baseurl, cookie_header, lacpport[i]) 34 | # --------------- Get LACP ports with trunk --------------- # 35 | lacpports = ports.get_lacp_port(baseurl, cookie_header) 36 | for i in (range(len(lacpports['lacp_element']))): 37 | print("Port ID: {}, Trunk Group: {} ".format(lacpports['lacp_element'][i]['port_id'], 38 | lacpports['lacp_element'][i]['trunk_group'])) 39 | # --------------- Associate a port to a vlan (show vlan ) --------------- # 40 | vlanport = data['vlanport'] 41 | for x in range(len(vlanport)): 42 | vlan.create_vlan_with_port(baseurl, vlanport[x], cookie_header) 43 | # --------------- Create Device profile and device identity --------------- # 44 | devicep1 = data['deviceprofile1'] 45 | deviceidentity.create_deviceprofile(baseurl, devicep1, cookie_header) 46 | devicei = data['deviceidentity'] 47 | deviceidentity.create_deviceidentity(baseurl, devicei, cookie_header) 48 | # --------------- Run Cable Diagnostics Tests --------------- # 49 | ports.clear_cable_diagnostics(baseurl, cookie_header) 50 | ports.test_cable_diagnosticsrange(baseurl, cookie_header, data['portlist']) 51 | ports.get_cable_diagnostics(baseurl, cookie_header) 52 | except Exception as error: 53 | print('Ran into exception: {}. Logging out..'.format(error)) 54 | finally: 55 | loginOS.logout(baseurl, cookie_header) 56 | -------------------------------------------------------------------------------- /src/ip_static_route.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module to GET and configure IP static routes. 3 | """ 4 | 5 | import requests 6 | import json 7 | from src import common 8 | 9 | 10 | def get_ip_route(baseurl, cookie_header): 11 | """ 12 | Get ip static route data 13 | :param baseurl: imported baseurl variable 14 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 15 | :return: ip-route REST call response data 16 | """ 17 | url = baseurl + 'ip-route' 18 | headers = {'cookie': cookie_header} 19 | rib = requests.get(url, verify=False, headers=headers) 20 | if rib.status_code == 200: 21 | return rib 22 | else: 23 | print("Get IP Route failed") 24 | 25 | 26 | def print_static_route(rib): 27 | """ 28 | Print IP static route data to screen 29 | :param rib: Data returned from get_static_route() 30 | :return: Print data to screen 31 | """ 32 | print("Static Routes:") 33 | print("{0:20} {1:20} {2:20} {3:22} {4}".format("Destination", "Mask", "Gateway", "Distance/Metric", "ID")) 34 | for x in rib.json()['ip_route_element']: 35 | c0 = x['destination']['octets'] 36 | c1 = x['mask']['octets'] 37 | c2 = x['gateway']['octets'] 38 | c3 = x['distance'] 39 | c4 = x['metric'] 40 | c5 = x['id'] 41 | print("{0:20} {1:20} {2:20} {3}/{4:<20} {5}".format(c0, c1, c2, c3, c4, c5)) 42 | 43 | 44 | def configure_static_route(baseurl, cookie_header, destination, mask, gateway, mode='IRM_GATEWAY'): 45 | """ 46 | Configure an IP static route. 47 | :param baseurl: imported baseurl variable 48 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 49 | :param destination: Destination IP subnet. String. 50 | :param mask: IP subnet mask. String 51 | :param gateway: Next-hop IP address. String. 52 | :param mode: Static route mode, default to 'IRM_GATEWAY'. String. 53 | :return: POST call status code and print result to screen. 54 | """ 55 | url = baseurl + 'ip-route' 56 | headers = {'cookie': cookie_header} 57 | static_route = { 58 | 'destination': {'octets': destination, 'version': 'IAV_IP_V4'}, 59 | 'mask': {'octets': mask, 'version': 'IAV_IP_V4'}, 60 | 'gateway': {'octets': gateway, 'version': 'IAV_IP_V4'}, 61 | 'ip_route_mode': mode} 62 | conf_static = requests.post(url, verify=False, data=json.dumps(static_route), headers=headers) 63 | if conf_static.status_code == 201: 64 | print("Static route configuration OK - Code: {}".format(conf_static.status_code)) 65 | else: 66 | print("Static route config ERROR - Code: {}".format(conf_static.status_code)) 67 | 68 | 69 | def print_gateway(echo_response, gateway): 70 | """ 71 | Print a response to screen based upon icmp_response result to the static route next-hop 72 | :param echo_response: result of icmp_echo() 73 | :param gateway: IP address of IP destination gateway 74 | :return: Print result to screen 75 | """ 76 | if echo_response['result'] == 'PR_OK': 77 | print("Static Next-Hop Gateway {} is reachable.".format(gateway)) 78 | elif echo_response['result'] == 'PR_REQUEST_TIME_OUT': 79 | print("Static Next-Hop Gateway {} is unreachable, request timed out.".format(gateway)) 80 | else: 81 | print("Ping failed: {}".format(echo_response['result'])) 82 | 83 | 84 | def gateway_check(baseurl, host, cookie_header): 85 | echo_response = common.icmp_echo(baseurl, host, cookie_header) 86 | print_gateway(echo_response, host) 87 | -------------------------------------------------------------------------------- /src/vlan.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | 4 | 5 | def create_vlan(baseurl, cookie_header, vlan): 6 | """ 7 | Create VLAN in switch 8 | :param baseurl: imported baseurl variable 9 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 10 | :param vlan: data imported from yaml file specifying vlan details 11 | :return: Print status of vlan creation success/failure on screen 12 | """ 13 | url = baseurl + 'vlans' 14 | headers = {'cookie': cookie_header} 15 | response = requests.post(url, verify=False, data=json.dumps(vlan), headers=headers) 16 | if response.status_code == 201: 17 | print("Vlan ID: {} & VLAN Name: {} creation is successful" .format(vlan['vlan_id'], vlan['name'])) 18 | else: 19 | print("Vlan creation is not successful") 20 | 21 | 22 | def get_vlan(baseurl, cookie_header): 23 | """ 24 | Get all VLANs in switch 25 | :param baseurl: imported baseurl variable 26 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 27 | :return: Return all vlans in json format 28 | """ 29 | url = baseurl + 'vlans' 30 | headers = {'cookie': cookie_header} 31 | response = requests.get(url, verify=False, headers=headers) 32 | if response.status_code == 200: 33 | return response.json() 34 | 35 | 36 | def create_vlan_with_port(baseurl, vlanport, cookie_header): 37 | """ 38 | Associate VLAN to a port 39 | :param baseurl: imported baseurl variable 40 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 41 | :param vlanport: data imported from yaml file specifying vlan and port details 42 | :return: Print status of vlan to port association success/failure on screen 43 | """ 44 | url = baseurl + 'vlans-ports' 45 | headers = {'cookie': cookie_header} 46 | response = requests.post(url, verify=False, data=json.dumps(vlanport), headers=headers) 47 | if response.status_code == 201: 48 | print("Vlan {} assignment to the port {} is successful" .format((vlanport['vlan_id']), vlanport['port_id'])) 49 | else: 50 | print("Vlan assignment to the port is not successful") 51 | 52 | 53 | def delete_vlans(baseurl, cookie_header, vlanids): 54 | """ 55 | Delete a Vlan in the switch 56 | :param baseurl: imported baseurl variable 57 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 58 | :param vlanids: data imported from yaml file specifying vlans to be deleted 59 | :return: Print status of vlan to port association success/failure on screen 60 | """ 61 | url = baseurl + 'vlans/'+str(vlanids) 62 | headers = {'cookie': cookie_header} 63 | response = requests.delete(url, verify=False, headers=headers) 64 | if response.status_code == 204: 65 | print("Vlan {} deletion is successful" .format(vlanids)) 66 | else: 67 | print("Vlan {} deletion is not successful" .format(vlanids)) 68 | 69 | 70 | def get_ip_addresses(baseurl, cookie_header): 71 | """ 72 | This functions queryies the switch's VLAN assigned IP addresses. 73 | :return: Response IP address data. Check the API schema for details. 74 | """ 75 | 76 | url = baseurl + 'ipaddresses' 77 | headers = {'cookie': cookie_header} 78 | 79 | try: 80 | response = requests.get(url, headers=headers, verify=False) 81 | if response.status_code == 200 | response.status_code <= 226: 82 | return response 83 | except (requests.RequestException, requests.ConnectionError, requests.HTTPError) as error: 84 | print('\nRequests module exception: {}'.format(error.args)) 85 | -------------------------------------------------------------------------------- /workflows/monitoring.py: -------------------------------------------------------------------------------- 1 | from src import loginOS, poe, ports, snmp 2 | from sampledata import getyaml 3 | from requests.packages.urllib3 import disable_warnings 4 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 5 | 6 | disable_warnings(InsecureRequestWarning) 7 | 8 | data = getyaml.readyaml() 9 | for ip in data['ipadd']: 10 | baseurl = "https://{}/rest/v4/".format(ip) 11 | try: 12 | cookie_header = loginOS.login_os(data, baseurl) 13 | 14 | # --------------- Write all POE stats into Excel MonitorPOE.xlsx --------------- # 15 | poe.monitor_poe(baseurl, cookie_header) 16 | 17 | # --------------- Write all Port statistics to an Excel - PortStats.xlsx--------------- # 18 | ports.get_ports_stats_to_excel(baseurl, cookie_header) 19 | 20 | # --------------- Monitor a Port and if the TX rate increases, dynamically configure rate limit for the port # 21 | port = data['ratelimitport'] 22 | ports.monitor_port(baseurl, cookie_header, port) 23 | 24 | # --------------- Monitor Transceivers --------------- # 25 | transceivers = ports.get_transceivers(baseurl, cookie_header) 26 | transceivers_serials = [] 27 | if len(transceivers['transceiver_element']) > 0: 28 | for serial in transceivers['transceiver_element']: 29 | transceivers_serials.append(serial['serial_number']) 30 | 31 | # print transceivers port number, type and serial number 32 | for transceiver in transceivers_serials: 33 | transceiver_detail = ports.get_transceiver_detail(baseurl, cookie_header, transceiver) 34 | print("Transceiver on port {} is Type: {} Serial Number: {}" 35 | .format(transceiver_detail['port_id'], transceiver_detail['type'], 36 | transceiver_detail['serial_number'])) 37 | transceiver_diag = ports.get_transceiver_diagnostics(baseurl, cookie_header, transceiver) 38 | 39 | # Print the temperature and electricity usage of transceivers, if type is DOM 40 | if transceiver_diag['diagnoistic_support_type'] == 'TDS_DOM': 41 | print( 42 | "Temperature in Celsius: {}, Voltage: {}, Tx bias(mA), Tx Power(dbm): {}, Rx Power(dbm): {}" 43 | .format(transceiver_diag['dom_diagnostics']['temperature_in_degree_celsius'], 44 | transceiver_diag['dom_diagnostics']['voltage_in_volts'], 45 | transceiver_diag['dom_diagnostics']['tx_bias_in_milliampere'], 46 | transceiver_diag['dom_diagnostics']['tx_power_in_dbm'], 47 | transceiver_diag['dom_diagnostics']['rx_power_in_dbm'] 48 | )) 49 | 50 | # --------------- Get Port id of the given client mac address --------------- # 51 | macaddress = data['macaddress'] 52 | mactable = ports.get_mac_table(baseurl, cookie_header) 53 | for i in mactable['mac_table_entry_element']: 54 | if i['mac_address'] == macaddress: 55 | portid = i['port_id'] 56 | print("Port ID for the mac address {} is: {}".format(macaddress, portid)) 57 | # --------------- Enable/Disable SNMP trap for the device port depends on OUI --------------- # 58 | snmp.config_snmp_hosts(baseurl, data['snmphosts'], cookie_header) 59 | snmp.snmp_linktrap_enable(data['linkport'], baseurl, cookie_header) 60 | except Exception as error: 61 | print('Ran into exception: {}. Logging out..'.format(error)) 62 | finally: 63 | loginOS.logout(baseurl, cookie_header) 64 | -------------------------------------------------------------------------------- /src/poe.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import requests 3 | from src import common 4 | from tools import ExcelOps 5 | 6 | 7 | def monitor_poe(baseurl, cookie_header): 8 | """ 9 | Write POE details to an excel sheet - MonitorPOE.xlsx in the same folder 10 | :param baseurl: imported baseurl variable 11 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 12 | :return: Write below POE details of each port to excel MonitorPOE.xlsx if exits, 13 | otherwise create a new excel with same name: 14 | Allocated Power(w) 15 | POE Enabled 16 | POE Allocation Method 17 | POE Priority 18 | Pre-Standard Dectect Enabled 19 | Actual Power(w) 20 | MPS Absent Count 21 | Over Current Count 22 | POE Detection Status 23 | Port Voltage(v) 24 | Power ClassPower Denied Count 25 | Power Short Count 26 | """ 27 | ports = get_ports(baseurl, cookie_header) 28 | poedata = [["Port ID", "Allocated Power(w)", "POE Enabled", "POE Allocation Method", 29 | "POE Priority", "Pre-Standard Dectect Enabled", "Actual Power(w)", "MPS Absent Count", 30 | "Over Current Count", "POE Detection Status", "Port Voltage(v)", "Power Class" 31 | "Power Denied Count", 32 | "Power Short Count"]] 33 | excelname = "MonitorPOE.xlsx" 34 | sheet = "POEstats" 35 | for i in range(len(ports)): 36 | url = baseurl + 'ports/' + ports[i] + '/poe' 37 | headers = {'cookie': cookie_header} 38 | response = requests.get(url, verify=False, headers=headers) 39 | if response.status_code == 200: 40 | poeconf = response.json() 41 | 42 | url = baseurl + 'ports/' + ports[i] + '/poe/stats' 43 | headers = {'cookie': cookie_header} 44 | response = requests.get(url, verify=False, headers=headers) 45 | print("POE Statistics for port {} collected successfully".format(ports[i])) 46 | if response.status_code == 200: 47 | poestats = response.json() 48 | if poestats['poe_detection_status'] == 'Fault': 49 | print("POE dection shows Faulty for port {}".format(ports[i])) 50 | poe_recycle(baseurl, cookie_header, ports[i]) 51 | 52 | '''Create Main list to write to excel''' 53 | poei = [poeconf['port_id'], poeconf['allocated_power_in_watts'], poeconf['is_poe_enabled'], 54 | poeconf['poe_allocation_method'], 55 | poeconf['poe_priority'], poeconf['pre_standard_detect_enabled'], poestats['actual_power_in_watts'], 56 | poestats['mps_absent_count'], 57 | poestats['over_current_count'], poestats['poe_detection_status'], poestats['port_voltage_in_volts'], 58 | poestats['power_class'], 59 | poestats['power_denied_count'], poestats['short_count']] 60 | poedata.append(poei) 61 | ExcelOps.writetoexecl(excelname, sheet, poedata) 62 | 63 | 64 | def get_ports(baseurl, cookie_header): 65 | """ 66 | Get all ports ids from the switches - works for standalone and stacked switches 67 | :param baseurl: imported baseurl variable 68 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 69 | :return: Return all ports for a given standalone switch or stacked switch. 70 | """ 71 | url = baseurl + 'ports' 72 | headers = {'cookie': cookie_header} 73 | response = requests.get(url, verify=False, headers=headers) 74 | ports = [] 75 | if response.status_code == 200: 76 | portslist = response.json()['port_element'] 77 | for i in (range(len(portslist))): 78 | portid = (portslist[i]['id']) 79 | ports.append(portid) 80 | return ports 81 | 82 | 83 | def poe_recycle(baseurl, cookie_header, port): 84 | """ 85 | Bounce power on any given port on switch 86 | :param baseurl: imported baseurl variable 87 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 88 | :return: Disable and enable POE on any given port. Print the status on screen 89 | """ 90 | logging.info("Starting Power recycle for Port {}".format(port)) 91 | cmd = "configure terminal" 92 | common.anycli(baseurl, cmd, cookie_header) 93 | cmd = "interface " + port 94 | common.anycli(baseurl, cmd, cookie_header) 95 | cmd = "no power-over-ethernet" 96 | common.anycli(baseurl, cmd, cookie_header) 97 | logging.info("Power disabled for Port {}".format(port)) 98 | cmd = "power-over-ethernet" 99 | common.anycli(baseurl, cmd, cookie_header) 100 | logging.info("Power enabled for Port {}".format(port)) 101 | -------------------------------------------------------------------------------- /src/common.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import base64 4 | 5 | 6 | def anycli(baseurl, cmd, cookie_header): 7 | """ 8 | Send cli commands supported on ArubaOS switch via REST API. 9 | All configuration and execution commands in non-interactive mode are supported. 10 | This is not supported for : crypto, copy, process-tracking, recopy, redo, repeat, session, end, print, 11 | terminal, logout, menu, page, restore, update, upgrade-software, return, setup, screen-length, 12 | vlan range and help commands. 13 | Testmode commands are not supported. All show commands are supported except show tech and show history. 14 | Output of show command is encoded in base64. 15 | :param baseurl: imported baseurl variable 16 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 17 | :param cmd: cli command to be executed 18 | :return Return base64 encoded data for show command, also return success or failure status 19 | :Example: 20 | 21 | result = cli(url=base_url, auth=s, command="show vlan") 22 | print_anycli(result) 23 | """ 24 | url = baseurl + 'cli' 25 | headers = {'cookie': cookie_header} 26 | command = {"cmd": cmd} 27 | response = requests.post(url, verify=False, data=json.dumps(command), headers=headers) 28 | if response.status_code == 200: 29 | print("Executing the command '{}' is successful" .format(cmd)) 30 | return response.json() 31 | 32 | 33 | def print_anycli(**kwargs): 34 | """ 35 | List the number of deployed scripts on remote device 36 | :param kwargs: 37 | keyword value: value to display 38 | :return: display the result of AnyCLI 39 | :Example: 40 | 41 | result = cli(url=base_url, auth=s, command="show vlan") 42 | print_anycli(result) 43 | """ 44 | value = kwargs.get('value', None) 45 | print(base64.b64decode(value['result_base64_encoded']).decode('utf-8')) 46 | return base64.b64decode(value['result_base64_encoded']).decode('utf-8') 47 | 48 | 49 | def decoded_anycli(**kwargs): 50 | """ 51 | Return the decoded return from AnyCLI request - Do not print anything 52 | :param kwargs: 53 | keyword value: value to display 54 | :return: return the result of AnyCLI in UTF-8 55 | :Example: 56 | 57 | result = cli(url=base_url, auth=s, command="show vlan") 58 | decoded_anycli(result) 59 | """ 60 | value = kwargs.get('value', None) 61 | return base64.b64decode(value['result_base64_encoded']).decode('utf-8') 62 | 63 | 64 | def batchcli(cookie, **kwargs): 65 | """ 66 | Send a set of commands to the remote device through the Batch_CLI API 67 | :param cookie: Cookie value 68 | :param kwargs: 69 | keyword url: base url 70 | keyword commands_set: set of commands which has to be applied on remote device 71 | :return: Batch_CLI status code 72 | :Example: 73 | 74 | result = anycli(url=base_url, auth=s, commands="show vlan") 75 | or 76 | result = anycli(url=base_url, header=aoss_header, commands="show vlan") 77 | """ 78 | header = {'cookie': cookie} 79 | commands_set = kwargs.get('commands', None) 80 | encoded_data = base64.b64encode(commands_set.encode('utf-8')) 81 | data = { 82 | "cli_batch_base64_encoded": encoded_data.decode('utf-8') 83 | } 84 | post_batchcli = requests.post(kwargs["url"] + "cli_batch", data=json.dumps(data), headers=header, verify=False, 85 | timeout=2) 86 | return post_batchcli.status_code 87 | 88 | 89 | def icmp_echo(baseurl, host, cookie_header): 90 | """ 91 | Test IP connectivity to a given host 92 | :param baseurl: Imported from yaml 93 | :param host: IP address of destination 94 | :param cookie_header: Object created by loginOS.login_os() 95 | :return: REST call response JSON 96 | """ 97 | url = baseurl + 'ping' 98 | headers = {'cookie': cookie_header} 99 | ip = {'destination': {'ip_address': {'version': 'IAV_IP_V4', 'octets': host}}} 100 | response = requests.post(url, headers=headers, json=ip, verify=False) 101 | return response.json() 102 | 103 | 104 | def print_ping(echo_response, host): 105 | """ 106 | Print a response to screen based upon icmp_response result 107 | :param echo_response: result of icmp_echo() 108 | :param host: IP address of IP destination 109 | :return: Print result to screen 110 | """ 111 | if echo_response['result'] == 'PR_OK': 112 | print("IP address {} is reachable.".format(host)) 113 | elif echo_response['result'] == 'PR_REQUEST_TIME_OUT': 114 | print("ERROR! IP address {} is unreachable, request timed out.".format(host)) 115 | else: 116 | print("Ping failed: {}".format(echo_response['result'])) 117 | 118 | 119 | def ping(baseurl, host, cookie_header): 120 | """ 121 | Combine icmp_echo and print_ping functions 122 | :param baseurl: Imported from yaml 123 | :param host: IP address of destination 124 | :param cookie_header: Object created by loginOS.login_os() 125 | :return: Prints to screen via print_ping() 126 | """ 127 | echo_response = icmp_echo(baseurl, host, cookie_header) 128 | print_ping(echo_response, host) 129 | -------------------------------------------------------------------------------- /src/lldp.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | """ 5 | Modules Import 6 | """ 7 | import requests 8 | import json 9 | 10 | """ 11 | Functions which use LLDP APIs on AOS-Switch 12 | """ 13 | 14 | 15 | def lldp_status(url, cookie): 16 | """ 17 | Retrieve Device's LLDP Global Status 18 | :param url: base url 19 | :param cookie: Cookie Value 20 | :return: LLDP Global Status JSON 21 | :Example: 22 | 23 | result = lldp_status(base_url, sessionid) 24 | """ 25 | header = {'cookie': cookie} 26 | get_lldp_status = requests.get(url + "lldp", headers=header, verify=False, timeout=2) 27 | return get_lldp_status.json() 28 | 29 | 30 | def print_lldpstatus(value): 31 | """ 32 | Print LLDP Global Status 33 | :param value: value to display 34 | :return: display the LLDP Remote Devices Information 35 | """ 36 | if value['admin_status'] == 'LLAS_ENABLED': 37 | print("\n LLDP is globally enabled") 38 | else: 39 | print("\n LLDP is globally disabled") 40 | 41 | 42 | def lldp_local(url, cookie): 43 | """ 44 | Retrieve LLDP Local Information 45 | :param url: base url 46 | :param cookie: Cookie Value 47 | :return: LLDP per local port information 48 | :Example: 49 | 50 | result = lldp_local(base_url, sessionid) 51 | """ 52 | header = {'cookie': cookie} 53 | get_lldp_local = requests.get(url + "lldp/local-port", headers=header, verify=False, timeout=2) 54 | return get_lldp_local.json() 55 | 56 | 57 | def lldp_remote(url, cookie): 58 | """ 59 | Retrieve LLDP Remote Information 60 | :param url: base url 61 | :param cookie: Cookie Value 62 | :return: LLDP Remote Devices Information 63 | :Example: 64 | 65 | result = lldp_remote(base_url, sessionid) 66 | """ 67 | header = {'cookie': cookie} 68 | get_remote = requests.get(url + "lldp/remote-device", headers=header, verify=False, timeout=2) 69 | return get_remote.json() 70 | 71 | 72 | def print_lldpremote(values): 73 | """ 74 | Print LLDP Remote Information 75 | :param values: value to display 76 | :return: display the LLDP Remote Devices Information 77 | :Example: 78 | 79 | result = lldp_remote(base_url, sessionid) 80 | for device in result: 81 | print_lldpremote(result) 82 | 83 | Device's Name : Aruba-Stack-3810M 84 | Device's IP : 10.105.100.6 85 | Device's Description : Aruba JL073A 3810M-24G-PoE+-1-slot Switch 86 | Device's Description : revision KB.16.04.0008 87 | Device's Description : ROM KB.16.01.0008 (/ws/swbuildm/rel_ukiah_qaoff/code/build/bom 88 | (swbuildm_rel_ukiah_qaoff_rel_ukiah)) 89 | Device's PVID : 100 90 | """ 91 | for value in values['lldp_remote_device_element']: 92 | if 'system_name' in value and value['system_name'] != '': 93 | print("\n Device's Name : {} - Connected on local port {}".format(value['system_name'], 94 | value['local_port'])) 95 | else: 96 | print("\n Connected on local port {}".format(value['local_port'])) 97 | if 'remote_management_address' in value: 98 | print("\tDevice's IP : {}".format(value['remote_management_address']['address'])) 99 | sys_desc = value['system_description'].split(',') 100 | for info in sys_desc: 101 | print("\tDevice's Description : {}".format(info)) 102 | print("\tDevice's PVID : {}".format(value['pvid'])) 103 | 104 | 105 | def lldp_renameport(url, device, cookie): 106 | """ 107 | Rename port based on given information 108 | :param url: Base URL 109 | :param device: Device's Information List 110 | :param cookie: Cookie Value 111 | :return: Requests Status Code 112 | """ 113 | header = {'cookie': cookie} 114 | data = { 115 | "id": device['local_port'], 116 | "name": "Remote_Device:{}_-_Remote_Port:{}".format(device['system_name'], device['port_description']) 117 | } 118 | put_port_name = requests.put(url + "ports/" + device['local_port'], data=json.dumps(data), headers=header, 119 | verify=False, timeout=2) 120 | if put_port_name.status_code == 200: 121 | print('Changing port {} with name \"{}\" is successful' .format(device['local_port'], data['name'])) 122 | else: 123 | print("An error occured - Status Code : {} - Content {}".format(put_port_name.status_code, 124 | put_port_name.content)) 125 | return put_port_name.status_code 126 | 127 | 128 | def lldp(url, cookie, **kwargs): 129 | """ 130 | LLDP general function 131 | :param url: Base URL 132 | :param cookie: Cookie Value 133 | :param kwargs: 134 | keyword info: Type of Info which has to be retrieve - "local", "remote" or "global" values are supported 135 | :return: return the sub-function result 136 | :Example: 137 | 138 | result = cli(base_url, sessionid, info="local") 139 | or 140 | result = cli(base_url, sessionid, info="remote") 141 | or 142 | result = cli(base_url, sessionid) 143 | """ 144 | info = kwargs.get('info', None) 145 | if info is not None and info == "local": 146 | result = lldp_local(url, cookie) 147 | to_return = result 148 | elif info is not None and info == "remote": 149 | result = lldp_remote(url, cookie) 150 | to_return = result 151 | else: 152 | result = lldp_status(url, cookie) 153 | to_return = result 154 | return to_return 155 | -------------------------------------------------------------------------------- /src/snmp.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | 4 | 5 | def config_snmp_hosts(baseurl, snmphosts, cookie_header): 6 | """ 7 | This function configure SNMP hosts 8 | 9 | :param baseurl: The resource URL for the device, in String format. 10 | :param cookie_header: The login cookie for the session. 11 | :param snmphosts: snmp hosts parsed from yaml file 12 | :return: print success or failure of SNMP hosts creation on screen 13 | """ 14 | url = baseurl + 'snmp-server/hosts' 15 | headers = {'cookie': cookie_header} 16 | response = requests.post(url, verify=False, data=json.dumps(snmphosts), headers=headers) 17 | print("response status is", response.status_code) 18 | if response.status_code == 201: 19 | print("SNMP Hosts configuration successful for {}".format(snmphosts['host_ip']['octets'])) 20 | else: 21 | print("SNMP Hosts configuration failed") 22 | 23 | 24 | def snmp_linktrap_enable(linktrap, baseurl, cookie_header): 25 | """ 26 | This function enable snmp traps for a port enable/disable 27 | 28 | :param baseurl: The resource URL for the device, in String format. 29 | :param cookie_header: The login cookie for the session. 30 | :param linktrap: port data is parsed from yaml file to enable or disable port details 31 | :return: Prints rap enables or failure on screen 32 | """ 33 | url = baseurl + 'snmp-server/traps/linktraps/' + linktrap['port_id'] 34 | headers = {'cookie': cookie_header} 35 | response = requests.put(url, verify=False, data=json.dumps(linktrap), headers=headers) 36 | print("response status is", response.status_code) 37 | if response.status_code == 200: 38 | print("SNMP Link trap status is {} for port - {}".format((linktrap['is_enabled']), linktrap['port_id'])) 39 | else: 40 | print("Update of link trap enable for port {} failed".format(linktrap['port_id'])) 41 | 42 | 43 | def get_snmp_communities(baseurl, cookie_header): 44 | """ 45 | This function gets all of the SNMP Communities from the device and returns their info in JSON format 46 | 47 | :param baseurl: The resource URL for the device, in String format. 48 | :param cookie_header: The login cookie for the session. 49 | :return: JSON data containing all of the SNMP Communites on the switch. 50 | """ 51 | url = baseurl + 'snmp-server/communities' 52 | headers = {'cookie': cookie_header} 53 | response = requests.get(url, verify=False, headers=headers) 54 | if response.status_code == 200: 55 | return response.json() 56 | 57 | 58 | def create_snmp_community(baseurl, cookie_header, name, community): 59 | """ 60 | This function creates a new SNMP Community with the 'name' parameter 61 | 62 | :param baseurl: The resource URL for the device, in String format. 63 | :param cookie_header: The login cookie for the session. 64 | :param name: The SNMP Community Name to be created, 1-32 digits, in String format 65 | :param community: JSON passed from the get_snmp_community call 66 | :return: return status code of the API call 67 | """ 68 | url = baseurl + 'snmp-server/communities' 69 | headers = {'cookie': cookie_header} 70 | data = { 71 | "access_type": community['access_type'], 72 | "community_name": name, 73 | "restricted": community['restricted'] 74 | } 75 | print(data) 76 | response = requests.post(url, verify=False, data=json.dumps(data), headers=headers) 77 | if response.status_code == 201: 78 | print("SNMP Community Created successfully for {}".format(data['community_name'])) 79 | return response.status_code 80 | else: 81 | print("SNMP Community Creation Failed - " + str(response)) 82 | 83 | 84 | def update_snmp_community(baseurl, cookie_header, name, access_type="UT_OPERATOR", restricted=True): 85 | """ 86 | This function updates a SNMP Community with the 'name' parameter, to change the access type or restriction 87 | 88 | :param baseurl: The resource URL for the device, in String format. 89 | :param cookie_header: The login cookie for the session. 90 | :param name: The SNMP Community Name to be updated, in String format 91 | :param access_type: The updated access level of the SNMP Community. The options are UT_OPERATOR and UT_MANAGER. 92 | This is defaulted in the function to UT_OPERATOR. 93 | :param restricted: The type of access for community, in Boolean format. True means the community is restricted, 94 | False means the community is unrestricted. 95 | :return: prints SNMP update status on screen 96 | """ 97 | url = baseurl + 'snmp-server/communities/' + name 98 | headers = {'cookie': cookie_header} 99 | data = { 100 | "access_type": access_type, 101 | "community_name": name, 102 | "restricted": restricted 103 | } 104 | response = requests.put(url, verify=False, data=json.dumps(data), headers=headers) 105 | if response.status_code == 200: 106 | print("SNMP Community Update successful for {}".format(data['community_name'])) 107 | else: 108 | print("SNMP Community Update Failed - " + str(response)) 109 | 110 | 111 | def delete_snmp_community(baseurl, cookie_header, name): 112 | """ 113 | This function deletes a SNMP Community by the 'name' parameter. 114 | 115 | :param baseurl: The resource URL for the device, in String format. 116 | :param cookie_header: The login cookie for the session. 117 | :param name: The SNMP Community Name to be deleted, in String format 118 | :return: Print delete operation success or failure 119 | """ 120 | url = baseurl + 'snmp-server/communities/' + name 121 | headers = {'cookie': cookie_header} 122 | data = { 123 | "community_name": name 124 | } 125 | response = requests.delete(url, verify=False, data=json.dumps(data), headers=headers) 126 | if response.status_code == 204: 127 | print("SNMP Community Deletion for {} successful.".format(data['community_name'])) 128 | else: 129 | print("SNMP Community Deletion Failed - " + str(response)) 130 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | If you're reading this, you're probably thinking about contributing to this repository. We really appreciate that--thank you! 4 | 5 | This document provides guidelines on contributing to this repository. Please follow these guidelines when creating issues, making commits, and submitting pull requests. The repository maintainers review all pull requests and verify that they conform to these guidelines before approving and merging. 6 | 7 | #### Table Of Contents 8 | [How Can I Contribute?](#how-can-i-contribute) 9 | * [Contribution Ideas](#contribution-ideas) 10 | * [What should I know before I get started?](#what-should-i-know-before-i-get-started) 11 | 12 | [Licensing](#licensing) 13 | * [Developer's Certificate of Origin](#developers-certificate-of-origin) 14 | * [Sign Your Work](#sign-your-work) 15 | 16 | [Coding Conventions](#coding-conventions) 17 | 18 | [Additional Notes](#additional-notes) 19 | * [Resources](#resources) 20 | 21 | ## How Can I Contribute? 22 | 23 | ### Contribution Ideas 24 | 25 | 1. Raise issues for bugs, features, and enhancements. 26 | 1. Submit updates and improvements to the documentation. 27 | 1. Submit articles and guides, which are also part of the documentation. 28 | 1. Help out repo maintainers by answering questions in [Airheads Developer Community][airheads-link]. 29 | 1. Share feedback and let us know about interesting use cases in [Airheads Developer Community][airheads-link]. 30 | 31 | ### What should I know before I get started? 32 | 33 | The best way to directly collaborate with the project contributors is through GitHub. 34 | 35 | * If you want to raise an issue such as a defect, an enhancement request, feature request, or a general issue, please open a GitHub issue. 36 | * If you want to contribute to our code by either fixing a problem, enhancing some code, or creating a new feature, please open a GitHub pull request against the development branch. 37 | > **Note:** All pull requests require an associated issue number, must be made against the **development** branch, and require acknowledgement of the DCO. See the [Licensing](#licensing) section below. 38 | 39 | Before you start to code, we recommend discussing your plans through a GitHub issue, especially for more ambitious contributions. This gives other contributors a chance to point you in the right direction, give you feedback on your design, and help you find out if someone else is working on the same thing. 40 | 41 | It is your responsibility to test and verify, prior to submitting a pull request, that your updated code doesn't introduce any bugs. Please write a clear commit message for each commit. Brief messages are fine for small changes, but bigger changes warrant a little more detail (at least a few sentences). 42 | Note that all patches from all contributors get reviewed. 43 | After a pull request is made, other contributors will offer feedback. If the patch passes review, a maintainer will accept it with a comment. 44 | When a pull request fails review, the author is expected to update the pull request to address the issue until it passes review and the pull request merges successfully. 45 | 46 | At least one review from a maintainer is required for all patches. 47 | 48 | ## Licensing 49 | 50 | All contributions must include acceptance of the DCO: 51 | 52 | ### Developer’s Certificate of Origin 53 | 54 | > Developer Certificate of Origin Version 1.1 55 | > 56 | > Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 660 57 | > York Street, Suite 102, San Francisco, CA 94110 USA 58 | > 59 | > Everyone is permitted to copy and distribute verbatim copies of this 60 | > license document, but changing it is not allowed. 61 | > 62 | > Developer's Certificate of Origin 1.1 63 | > 64 | > By making a contribution to this project, I certify that: 65 | > 66 | > \(a) The contribution was created in whole or in part by me and I have 67 | > the right to submit it under the open source license indicated in the 68 | > file; or 69 | > 70 | > \(b) The contribution is based upon previous work that, to the best of my 71 | > knowledge, is covered under an appropriate open source license and I 72 | > have the right under that license to submit that work with 73 | > modifications, whether created in whole or in part by me, under the same 74 | > open source license (unless I am permitted to submit under a different 75 | > license), as indicated in the file; or 76 | > 77 | > \(c) The contribution was provided directly to me by some other person 78 | > who certified (a), (b) or (c) and I have not modified it. 79 | > 80 | > \(d) I understand and agree that this project and the contribution are 81 | > public and that a record of the contribution (including all personal 82 | > information I submit with it, including my sign-off) is maintained 83 | > indefinitely and may be redistributed consistent with this project or 84 | > the open source license(s) involved. 85 | 86 | ### Sign Your Work 87 | 88 | To accept the DCO, simply add this line to each commit message with your 89 | name and email address (`git commit -s` will do this for you): 90 | 91 | Signed-off-by: Jane Example 92 | 93 | For legal reasons, no anonymous or pseudonymous contributions are 94 | accepted. 95 | 96 | ## Coding Conventions 97 | 98 | 1. Python code should conform to PEP-8. PyCharm editor has a built-in PEP-8 checker. 99 | 1. Since this is a collaborative project, document your code with comments that will help other contributors understand the code you write. 100 | 1. When in doubt, follow conventions you see used in the source already. 101 | 102 | ## Additional Notes 103 | 104 | > **Note:** Please don't file an issue to ask a question. Please reach out to us via email or disucssion forums. 105 | 106 | ### Resources 107 | 108 | | Resource | Description | 109 | | --- | --- | 110 | | [Airheads Developer Community][airheads-link] | Aruba Airheads forum to discuss all things network automation. | 111 | | [Aruba Bots Automate Videos][aruba-bots-playlist-link]| YouTube playlist containing instructional videos for Ansible and Python automation repositories. | 112 | | [aruba-switching-automation@hpe.com][email-link] | Distribution list email to contact the switching automation technical marketing engineering team. | 113 | 114 | 115 | [airheads-link]: https://community.arubanetworks.com/t5/Developer-Community/bd-p/DeveloperCommunity 116 | [aruba-bots-playlist-link]: https://www.youtube.com/playlist?list=PLsYGHuNuBZcYzoh7OIWLTyBJf-ahvE70k 117 | [email-link]: mailto:aruba-switching-automation@hpe.com 118 | -------------------------------------------------------------------------------- /src/system.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | 5 | def get_device_users(baseurl, cookie_header): 6 | """ 7 | This function gets all of the users from the device and returns their info in JSON format 8 | 9 | :param baseurl: The resource URL for the device, in String format 10 | :param cookie_header: The login cookie for the session 11 | :return: JSON data containing all of the users on the switch 12 | """ 13 | url = baseurl + 'management-user' 14 | headers = {'cookie': cookie_header} 15 | response = requests.get(url, verify=False, headers=headers) 16 | if response.status_code == 200: 17 | return response.json() 18 | 19 | 20 | def create_device_user(baseurl, cookie_header, name, password, auth_type="UT_OPERATOR", password_type="PET_PLAIN_TEXT"): 21 | """ 22 | This function creates a user with the associated 'name' and 'password' parameters. Note that the 'type' parameter 23 | is the Key value for the schema, and is defaulted to UT_OPERATOR. 24 | 25 | :param baseurl: The resource URL for the device, in String format 26 | :param cookie_header: The login cookie for the session 27 | :param name: The username to be created, 1-64 digits, in String format 28 | :param password: The password for the user being created, 1-64 digits, in String format. 29 | :param auth_type: The authority level of the user being created. The options are UT_OPERATOR and UT_MANAGER. 30 | This is a key value for the schema, and is defaulted in the function to UT_OPERATOR. 31 | :param password_type: The password encryption type to be used. The options are PET_PLAIN_TEXT and PET_SHA1. The 32 | default value for this function is PET_PLAIN_TEXT. 33 | :return: N/A 34 | """ 35 | url = baseurl + 'management-user' 36 | headers = {'cookie': cookie_header} 37 | data = { 38 | "type": auth_type, 39 | "name": name, 40 | "password": password, 41 | "password_type": password_type 42 | } 43 | # print(data) 44 | response = requests.put(url, verify=False, data=json.dumps(data), headers=headers) 45 | # print("response status is", response.status_code) 46 | if response.status_code == 201: 47 | print("User Update successful for {}".format(data['name'])) 48 | else: 49 | print("User Update Failed - " + str(response)) 50 | 51 | 52 | def update_device_user(baseurl, cookie_header, password, user): 53 | """ 54 | This function updates a user by the 'name' parameter and updates its password. Note that the 'type' parameter is 55 | the Key value for the schema, and is defaulted to UT_OPERATOR. 56 | 57 | :param baseurl: The resource URL for the device, in String format 58 | :param cookie_header: The login cookie for the session 59 | :param password: The new password for the user being updated, 1-64 digits, in String format. 60 | :param user: The JSON containing the user data 61 | :return: N/A 62 | """ 63 | url = baseurl + 'management-user/' + user['type'] 64 | # print(url) 65 | headers = {'cookie': cookie_header} 66 | data = { 67 | "type": user['type'], 68 | "name": user['name'], 69 | "password": password, 70 | "password_type": user['password_type'] 71 | } 72 | print(data) 73 | response = requests.put(url, verify=False, data=json.dumps(data), headers=headers) 74 | print("response status is", response.status_code) 75 | if response.status_code == 200: 76 | print("User Update successful for {}".format(data['name'])) 77 | return response.status_code 78 | else: 79 | print("User Update Failed - " + str(response)) 80 | 81 | 82 | def upload_tacert(baseurl, cookie_header, cert_name, cert_file): 83 | """ 84 | This function uploads the tacert to the switches. 85 | 86 | :param baseurl: The resource URL for the device, in String format 87 | :param cookie_header: The login cookie for the session 88 | :param cert_name: Name of the TA cert 89 | :param cert_file: Path of teh cert file 90 | :return: N/A 91 | """ 92 | url = baseurl + 'ta_profiles' 93 | # print(url) 94 | headers = {'cookie': cookie_header} 95 | with open(cert_file, 'rb') as file: 96 | cert_base64 = base64.b64encode(file.read()).decode('utf-8') 97 | 98 | data = { 99 | "ta_name" : cert_name, 100 | "ta_certificate_base64_encoded_pem" : cert_base64 101 | } 102 | response = requests.post(url, verify=False, data=json.dumps(data), headers=headers) 103 | print("response status is", response.status_code) 104 | if response.status_code == 201: 105 | print("TA cert upload is successful") 106 | return response.status_code 107 | else: 108 | print("TA cert upload failed, status code:" + str(response)) 109 | 110 | 111 | def get_system_time(baseurl, cookie_header): 112 | """" 113 | This function returns the host system time configuration 114 | 115 | :param baseurl: The resource URL for the device, in String format 116 | :param cookie_header: the login cookie for the session 117 | :return Json data containing the system time configuration 118 | """ 119 | url = baseurl + 'system/time' 120 | headers = {'cookie': cookie_header} 121 | response = requests.get(url, verify=False, headers=headers) 122 | if response.status_code == 200: 123 | return response.json() 124 | else: 125 | return None 126 | 127 | 128 | def print_system_time_config(values): 129 | """ 130 | Function to print the system time configuration 131 | :param values: json response from GET call containing the values to be displayed 132 | :return N/A 133 | prints to the screen the configuration in list form 134 | """ 135 | print('Local UTC Offset: ' + str(values['local_utc_offset_in_seconds'])) 136 | if values['auto_adjust_dst']: 137 | print('Automatically adjust DST changes: Yes') 138 | else: 139 | print('Automatically adjust DST changes: No') 140 | print('DST Begins on Day: ' + str(values['custom_dst_begins_rule']['day_of_month'])) 141 | print('Of Month: ' + str(values['custom_dst_begins_rule']['month'])) 142 | print('DST Ends on Day: ' + str(values['custom_dst_end_rule']['day_of_month'])) 143 | print('Of Month: ' + str(values['custom_dst_end_rule']['month'])) 144 | if values['time_servers']: 145 | print(values['time_servers']) 146 | if values['use_sntp_unicast']: 147 | print('Using SNTP unicast') 148 | else: 149 | print('Not using SNTP unicast') 150 | print('Time Server Protocol: ' + str(values['time_server_protocol'])) 151 | print('\n\n') 152 | -------------------------------------------------------------------------------- /src/tn.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | """ 5 | Modules Import 6 | """ 7 | import requests 8 | import json 9 | from src import common 10 | 11 | """ 12 | Functions which use LLDP APIs on AOS-Switch 13 | """ 14 | 15 | 16 | def get_tnconf(url, cookie): 17 | """ 18 | Retrieve Tunneled Node's Configuration 19 | :param url: base url 20 | :param cookie: Cookie value 21 | :return: TN Configuration JSON 22 | :Example: 23 | 24 | result = get_tnconf(base_url, sessionid) 25 | """ 26 | header = {'cookie': cookie} 27 | get_tn_config = requests.get(url + "tunneled_node_server", headers=header, verify=False, timeout=2) 28 | return get_tn_config.json() 29 | 30 | 31 | def print_tnconfig(value): 32 | """ 33 | Print Tunneled-Node Configuration 34 | :param value: value to display 35 | :return: display Tunneled-Node Configuration 36 | :Example: 37 | 38 | result = tn_configuration(base_url, sessionid) 39 | print_tnconfig(result) 40 | 41 | Tunneled-Node is configured 42 | Mode : Per-User Tunneled-Node 43 | Tunneled-Node Status : Enabled 44 | Remote Controller : 10.14.10.50 45 | Backup Remote Controller : 10.14.10.51 46 | """ 47 | if value['is_tn_server_configured'] is True: 48 | print("Tunneled-Node is configured") 49 | if value['mode'] == "TNSM_ROLE_BASED": 50 | print("\tMode : Per-User Tunneled-Node") 51 | elif value['mode'] == "TNSM_PORT_BASED": 52 | print("\tMode : Per-Port Tunneled-Node") 53 | else: 54 | print("\tMode : Not Configured") 55 | if value['tn_server_status'] is True: 56 | print("\tTunneled-Node Status : Enabled") 57 | else: 58 | print("\tTunneled-Node Status : Disabled") 59 | if value['controller_ip'] is not None: 60 | print("\tRemote Controller : {}".format(value['controller_ip']['octets'])) 61 | if value['backup_controller_ip'] is not None: 62 | print("\tBackup Remote Controller : {}".format(value['backup_controller_ip']['octets'])) 63 | else: 64 | print("Tunneled-Node is not configured on this device") 65 | 66 | 67 | def tn_configuration(url, values, cookie, **kwargs): 68 | """ 69 | Configure TN globally 70 | :param url: base url 71 | :param values: TN Configuration values 72 | :param cookie: Cookie value 73 | :param kwargs: 74 | keyword ports: Tunneled Ports in case of PPTN implementation 75 | :return: Status Code 76 | :note: In case of PUTN, neither function is called 77 | :Example: 78 | 79 | result = tn_configuration(base_url, values, sessionid, port=ports_list) 80 | """ 81 | header = {'cookie': cookie} 82 | ports = kwargs.get('ports', None) 83 | if values['mode'] == "ROLE": 84 | mode = "TNSM_ROLE_BASED" 85 | elif values['mode'] == "PORT": 86 | mode = "TNSM_PORT_BASED" 87 | print(mode) 88 | data = { 89 | "is_tn_server_configured": values['server_configured'], 90 | "controller_ip": {"version": "IAV_IP_V4", "octets": values['controller_ip']}, 91 | "tn_server_status": values['server_status'], 92 | "mode": str(mode) 93 | } 94 | print(data) 95 | put_tn_config = requests.put(url + "tunneled_node_server", data=json.dumps(data), headers=header, 96 | verify=False, timeout=2) 97 | 98 | if values['mode'] == "PORT" and ports is not None: 99 | print("Deploy TN configuration on Access Ports") 100 | result = tn_ports_config(url, ports[0]['ports_list'], cookie) 101 | if result == 1: 102 | put_tn_config = 200 103 | else: 104 | put_tn_config = 401 105 | return put_tn_config 106 | else: 107 | print(put_tn_config.status_code) 108 | return put_tn_config.status_code 109 | 110 | 111 | def tn_ports_config(url, values, cookie): 112 | """ 113 | Configure Access Ports in case of Per-Port Tunneled Node 114 | :param url: base url 115 | :param cookie: Cookie value 116 | :param values: TN Ports Configuration values 117 | :return: Status Code 118 | :note: Calls the create ports list and pptn_configuration functions 119 | :Example: 120 | 121 | result = tn_ports_config(base_url, ports, sessionid) 122 | """ 123 | header = {'cookie': cookie} 124 | ports_list = create_ports_list(values) 125 | result = 1 126 | for port in ports_list: 127 | data = { 128 | "port_id": port, 129 | "is_tn_server_applied": True, 130 | "is_fallback_local_switching": True 131 | } 132 | put_tn_config = requests.put(url + "tunneled_node_server/ports/" + port, data=json.dumps(data), headers=header, 133 | verify=False, timeout=2) 134 | if put_tn_config.status_code != 200: 135 | result = 0 136 | return result 137 | 138 | 139 | def create_ports_list(values): 140 | """ 141 | Create the ports list for PPTN configuration 142 | :param values: TN Ports Configuration values 143 | :return: the complete ports list in good format 144 | :Example: 145 | 146 | result = create_ports_lists(values) 147 | """ 148 | ports_list = values.split(',') 149 | for port in ports_list: 150 | if "-" in port: 151 | lists = port.split('-') 152 | i = int(lists[0]) 153 | while i <= int(lists[1]): 154 | ports_list.append(str(i)) 155 | i += 1 156 | ports_list.remove(port) 157 | return ports_list 158 | 159 | 160 | def pptn_configuration(url, port, cookie): 161 | """ 162 | Applies TN configuration on specified port 163 | :param url: base url 164 | :param cookie: Cookie value 165 | :param port: Port where TN will be activated 166 | :return: Port configuration Status Code 167 | :Example: 168 | 169 | result = pptn_configuration(base_url, port, sessionid) 170 | """ 171 | header = {'cookie': cookie} 172 | data = { 173 | "port_id": port, 174 | "is_tn_server_applied": True, 175 | "is_fallback_local_switching": True 176 | } 177 | put_pptn_config = requests.put(url + "tunneled_node_server/ports/" + port, data=json.dumps(data), headers=header, 178 | verify=False, timeout=2) 179 | return put_pptn_config.status_code 180 | 181 | 182 | def get_tnstats(url, cookie): 183 | """ 184 | Retrieve Tunneled Node's agregated statistics 185 | :param url: base url 186 | :param cookie: Cookie value 187 | :return: TN Statisctics JSON 188 | :Example: 189 | 190 | result = get_tnstats(base_url, sessionid) 191 | """ 192 | header = {'cookie': cookie} 193 | get_tn_stats = requests.get(url + "tunneled_node_server/ports/aggregate_statistics", headers=header, verify=False, 194 | timeout=2) 195 | return get_tn_stats.json() 196 | 197 | 198 | def print_tnstats(values): 199 | """ 200 | Print TN Agregated Statistics 201 | :param values: Agregated Statisctics Values 202 | :Example: 203 | 204 | result = print_tnstats(values) 205 | """ 206 | print("Tunneled-Node Server - Aggregated Statistics") 207 | if values['heartbeat_packets_sent'] is not None: 208 | print("- Heartbeat Packets Sent : {}".format(values['heartbeat_packets_sent'])) 209 | else: 210 | print("- Heartbeat Packets Sent : No values to display") 211 | if values['heartbeat_packets_received'] is not None: 212 | print("- Heartbeat Packets Received : {}".format(values['heartbeat_packets_received'])) 213 | else: 214 | print("- Heartbeat Packets Received : No values to display") 215 | if values['heartbeat_packets_invalid'] is not None: 216 | print("- Invalid Heartbeat Packets : {}".format(values['heartbeat_packets_invalid'])) 217 | else: 218 | print("- Invalid Heartbeat Packets : No values to display") 219 | if values['fragmented_packets_dropped'] is not None: 220 | print("- Fragmented Packets received : {}".format(values['fragmented_packets_dropped'])) 221 | else: 222 | print("- Fragmented Packets received : No values to display") 223 | if values['packets_to_non_existent_tunnel'] is not None: 224 | print("- Packets to Non Existent Tunnel : {}".format(values['packets_to_non_existent_tunnel'])) 225 | else: 226 | print("- Packets to Non Existent Tunnel : No values to display") 227 | if values['mtu_violation_drop'] is not None: 228 | print("- MTU Violation Drop : {}".format(values['mtu_violation_drop'])) 229 | else: 230 | print("- MTU Violation Drop : No values to display") 231 | 232 | 233 | def get_tn_users(url, cookie): 234 | """ 235 | Retrieve Tunneled Node's connected users 236 | :param url: base url 237 | :param cookie: Cookie value 238 | :return: TN users JSON 239 | :note: This function will display the result of the "show tunneled-node-users all" 240 | :Example: 241 | 242 | result = get_tn_users(base_url, sessionid) 243 | """ 244 | get_tunnel_users = common.anycli(url, "show tunneled-node-users all", cookie) 245 | status = common.decoded_anycli(value=get_tunnel_users) 246 | return status 247 | -------------------------------------------------------------------------------- /src/vsf.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | # Getters, functions to request data 5 | def get_global_config(baseurl, cookie_header): 6 | """" 7 | Fetch the global configuration of the VSF stack. 8 | :param baseurl: imported base url 9 | :param cookie_header: parse cookie resulting from successful loginOS.login_os(baseurl) 10 | :return VSF global configuration and members' status 11 | """ 12 | url = baseurl + 'stacking/vsf/global_config' 13 | headers = {'cookie': cookie_header} 14 | response = requests.get(url, verify=False, headers=headers) 15 | if response.status_code == 200: 16 | return response.json() 17 | else: 18 | return response.status_code 19 | 20 | 21 | def get_vsf_info(baseurl, cookie_header): 22 | """" 23 | Fetch the VSF information. 24 | :param baseurl: imported base url variable 25 | :param cookie_header: parse cookie resulting from successful loginOS.login_os(baseurl) 26 | :return VSF stack information 27 | """ 28 | url = baseurl + 'stacking/vsf/info' 29 | headers = {'cookie': cookie_header} 30 | response = requests.get(url, verify=False, headers=headers) 31 | if response.status_code == 200: 32 | return response.json() 33 | else: 34 | return response.status_code 35 | 36 | 37 | def get_vsf_members(baseurl, cookie_header): 38 | """" 39 | Fetch the VSF members' information. 40 | :param baseurl: imported base url variable 41 | :param cookie_header: parse cookie resulting from successful loginOS.login_os(baseurl) 42 | :return VSF members' information 43 | """ 44 | url = baseurl + 'stacking/vsf/members' 45 | headers = {'cookie': cookie_header} 46 | response = requests.get(url, verify=False, headers=headers) 47 | if response.status_code == 200: 48 | return response.json() 49 | else: 50 | return response.status_code 51 | 52 | 53 | def get_system_info(baseurl, cookie_header): 54 | """" 55 | Fetch the VSF system information. 56 | :param baseurl: imported base url variable 57 | :param cookie_header: parse cookie resulting from successful loginOS.login_os(baseurl) 58 | :return List of VSF member information 59 | """ 60 | url = baseurl + 'stacking/vsf/members/system_info' 61 | headers = {'cookie': cookie_header} 62 | response = requests.get(url, verify=False, headers=headers) 63 | if response.status_code == 200: 64 | return response.json() 65 | else: 66 | return response.status_code 67 | 68 | 69 | def get_members_links_ports(baseurl, cookie_header): 70 | """" 71 | Fetch the VSF links and ports information. 72 | :param baseurl: imported base url variable 73 | :param cookie_header: parse cookie resulting from successful loginOS.login_os(baseurl) 74 | :return VSF links and ports information 75 | """ 76 | url = baseurl + 'stacking/vsf/members_links_ports' 77 | headers = {'cookie': cookie_header} 78 | response = requests.get(url, verify=False, headers=headers) 79 | if response.status_code == 200: 80 | return response.json() 81 | else: 82 | return response.status_code 83 | 84 | 85 | def print_show_vsf(values1, values2, values3): 86 | """" 87 | Function to Print the Information of the specific Stack Members 88 | :param values1: contains the json from get_vsf_info call 89 | :param values2: contains the json from get_vsf_members call 90 | :param values3: contains the json from get_global_config call 91 | :return: none 92 | prints to the screen a similar output to that of cli command show vsf 93 | """ 94 | print('VSF Domain ID : %13d' % (values1['domain_id'])) 95 | print('MAC Address : %10s' % (values1['mac_address']['octets'])) 96 | print('VSF Topology : %13s' % (values1['topology'])) 97 | print('VSF Status : %13s' % (values1['status'])) 98 | print('Uptime : %d d %2d h %2d m' % (values1['uptime']['days'], 99 | values1['uptime']['hours'], 100 | values1['uptime']['minutes'])) 101 | if values3['is_oobm_mad_enabled']: 102 | print('VSF MAD : OOBM MAD enabled') 103 | elif values3['is_lldp_mad_enabled']: 104 | print('VSF MAD : LLDP MAD enabled') 105 | else: 106 | print('VSF MAD : MAD not enabled') 107 | print('VSF Port Speed : %13s' % (values1['port_speed'])) 108 | print('Software Version : %13s' % (values1['software_version'])) 109 | print('\nMember ') 110 | print('ID\tMAC Address\t\tModel\t\t\t\tPriority\tStatus') 111 | print('----------------------------------------------------------------') 112 | for member in range(len(values2['vsf_member_element'])): 113 | print('%d %3s %5s %4d %5s' % (values2['vsf_member_element'][member]['member_id'], 114 | values2['vsf_member_element'][member]['mac_address']['octets'], 115 | values2['vsf_member_element'][member]['model'], 116 | values2['vsf_member_element'][member]['priority'], 117 | values2['vsf_member_element'][member]['status']), end='\n\n') 118 | 119 | 120 | def get_member_config(baseurl, cookie_header): 121 | """" 122 | Fetch the VSF individual member configuration. 123 | :param baseurl: imported base url variable 124 | :param cookie_header: parse cookie resulting from successful loginOS.login_os(baseurl) 125 | :return: specified VSF member information 126 | """ 127 | member_id = int(input('\nEnter Member ID: ')) 128 | if member_id <= 0: 129 | print('Member IDs are positive integers, please check your ID') 130 | exit(1) 131 | url = baseurl + 'stacking/vsf/members/%i' % member_id 132 | headers = {'cookie': cookie_header} 133 | response = requests.get(url, verify=False, headers=headers) 134 | if response.status_code == 200: 135 | return response.json() 136 | else: 137 | return response.status_code 138 | 139 | 140 | def get_link_info(baseurl, cookie_header, member_id): 141 | """ 142 | Fetch the information on the links matching the link ID 143 | :param baseurl: imported base url variable 144 | :param cookie_header: parse cookie resulting from successful loginOS.login_os(baseurl) 145 | :param member_id: data imported from yaml file specifying the member ID 146 | :return: json with the list of VSF links matching the link ID 147 | """ 148 | url = baseurl + 'stacking/vsf/members/{}/links'.format(member_id) 149 | headers = {'cookie': cookie_header} 150 | response = requests.get(url, verify=False, headers=headers) 151 | if response.status_code == 200: 152 | return response.json() 153 | else: 154 | return response.status_code 155 | 156 | 157 | def get_member_link_info(baseurl, cookie_header, member_id, link_id): 158 | """ 159 | Fetch the information on the link of the matching member ID and link ID 160 | :param baseurl: imported base url variable 161 | :param cookie_header: parse cookie resulting from successful loginOS.login_os(baseurl) 162 | :param member_id: data imported from yaml file specifying the member ID 163 | :param link_id: data imported from yaml file specifying the link ID 164 | :return: json detailing the link configuration matching the set link and member ID 165 | """ 166 | url = baseurl + 'stacking/vsf/members/{}/links/{}'.format(member_id, link_id) 167 | headers = {'cookie': cookie_header} 168 | response = requests.get(url, verify=False, headers=headers) 169 | if response.status_code == 200: 170 | return response.json() 171 | else: 172 | return response.status_code 173 | 174 | 175 | def get_vsf_ports(baseurl, cookie_header, member_id, link_id): 176 | """ 177 | Fetch the information on the link of the matching member ID and link ID 178 | :param baseurl: imported base url variable 179 | :param cookie_header: parse cookie resulting from successful loginOS.login_os(baseurl) 180 | :param member_id: data imported from yaml file specifying the member ID 181 | :param link_id: data imported from yaml file specifying the link ID 182 | :return: json detailing the Port configuration matching the set link and member ID 183 | """ 184 | url = baseurl + 'stacking/vsf/members/{}/links/{}/ports'.format(member_id, link_id) 185 | headers = {'cookie': cookie_header} 186 | response = requests.get(url, verify=False, headers=headers) 187 | if response.status_code == 200: 188 | return response.json() 189 | else: 190 | return response.status_code 191 | 192 | 193 | def get_member_system_info(baseurl, cookie_header, member_id): 194 | """ 195 | Fetch the specified vsf member information 196 | :param baseurl: imported base url variable 197 | :param cookie_header: parse cookie resulting from successful loginOS.login_os(baseurl) 198 | :param member_id: data imported from yaml file specifying the member ID 199 | :return: json detailing the information matching the set member ID 200 | """ 201 | url = baseurl + 'stacking/vsf/members/system_info/{}'.format(member_id) 202 | headers = {'cookie': cookie_header} 203 | response = requests.get(url, verify=False, headers=headers) 204 | if response.status_code == 200: 205 | return response.json() 206 | else: 207 | return response.status_code 208 | -------------------------------------------------------------------------------- /src/ports.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import pprint 4 | import xlsxwriter 5 | from src import common 6 | 7 | 8 | def name_ports(baseurl, cookie_header, portname): 9 | """ 10 | Change port names to a given value 11 | :param baseurl: imported baseurl variable 12 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 13 | :param portname: data imported from yaml file 14 | :return: Print status of port name update success/failure on screen 15 | """ 16 | url = baseurl + 'ports/' + portname['id'] 17 | headers = {'cookie': cookie_header} 18 | response = requests.put(url, verify=False, data=json.dumps(portname), headers=headers) 19 | if response.status_code == 200: 20 | print('Changing port {} with name {} is successful'.format(portname['id'], portname['name'])) 21 | 22 | 23 | def create_lacp_port(baseurl, cookie_header, lacpport): 24 | """ 25 | Create LACP ports 26 | :param baseurl: imported baseurl variable 27 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 28 | :param lacpport: data imported from yaml file specified which port associated with which trunk profile 29 | :return: Print status of lacp port update success/failure on screen 30 | """ 31 | url = baseurl + 'lacp/port' 32 | headers = {'cookie': cookie_header} 33 | response = requests.post(url, verify=False, data=json.dumps(lacpport), headers=headers) 34 | if response.status_code == 201: 35 | print("LACP port {} association to Trunk profile {} is Successful".format((lacpport['port_id']), 36 | lacpport['trunk_group'])) 37 | else: 38 | print("LACP port Association to Trunk is not Successful, status code: {}".format(response.status_code)) 39 | 40 | 41 | def get_lacp_port(baseurl, cookie_header): 42 | """ 43 | Get LACP ports on switch 44 | :param baseurl: imported baseurl variable 45 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 46 | :return: retun all LACP ports configured on the switch 47 | """ 48 | url = baseurl + 'lacp/port' 49 | headers = {'cookie': cookie_header} 50 | response = requests.get(url, verify=False, headers=headers) 51 | if response.status_code == 200: 52 | return response.json() 53 | 54 | 55 | def get_cable_diagnostics(baseurl, cookie_header): 56 | """ 57 | Get Cable diagnostics results from the switch 58 | :param baseurl: imported baseurl variable 59 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 60 | :return: return cable diagnostics results for each ports in the switch 61 | """ 62 | url = baseurl + 'cable_diagnostics/status' 63 | headers = {'cookie': cookie_header} 64 | response = requests.get(url, verify=False, headers=headers) 65 | if response.status_code == 200: 66 | pprint.pprint(response.json()) 67 | 68 | 69 | def test_cable_diagnosticsrange(baseurl, cookie_header, portlist): 70 | """ 71 | Execute cable diagnostics tests for a given list of ports 72 | :param baseurl: imported baseurl variable 73 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 74 | :param portlist: imported data from yaml specifying list of ports where cable diagnostics tests needs to run 75 | :return: Print execution of cable diagnostics tests on given port(s) successful or not on screen 76 | """ 77 | for i in range(len(portlist)): 78 | port = {} 79 | url = baseurl + 'cable_diagnostics/port' 80 | headers = {'cookie': cookie_header} 81 | port['port_id'] = portlist[i] 82 | response = requests.post(url, verify=False, data=json.dumps(port), headers=headers) 83 | if response.status_code == 204: 84 | print("Cable Diagnostics Test for port {} initiated".format(portlist[i])) 85 | else: 86 | print("Cable Diagnostics test didn't start") 87 | 88 | 89 | def clear_cable_diagnostics(baseurl, cookie_header): 90 | """ 91 | Clear previous cable diagnostics test results on the switch 92 | :param baseurl: imported baseurl variable 93 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 94 | :return: Print execution status of resetting cable diagnostics tests on screen 95 | """ 96 | url = baseurl + 'cable_diagnostics/clear' 97 | headers = {'cookie': cookie_header} 98 | response = requests.post(url, verify=False, data=json.dumps({}), headers=headers) 99 | print("Clear cable Diagnostics Status code", response.status_code) 100 | if response.status_code == 204: 101 | print("Cable Diagnostics Test is cleared") 102 | else: 103 | print("Clear Cable Diagnostics test didn't go through") 104 | 105 | 106 | def get_ports_stats_to_excel(baseurl, cookie_header): 107 | """ 108 | Write port statistics to an excel sheet - PortStats.xlsx in the same folder 109 | :param baseurl: imported baseurl variable 110 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 111 | :return: Write below port statistics of each port to excel PortStats.xlsx if exits, 112 | otherwise create a new excel with same name: 113 | Total Bytes 114 | Total Packets 115 | Tx Drops 116 | Rx Error 117 | """ 118 | workbook = xlsxwriter.Workbook('PortStats.xlsx') 119 | worksheet = workbook.add_worksheet('Port Statistics') 120 | url = baseurl + 'port-statistics' 121 | headers = {'cookie': cookie_header} 122 | response = requests.get(url, verify=False, headers=headers) 123 | if response.status_code == 200: 124 | row = 0 125 | column = 0 126 | heading = ['Port ID', 'Total Bytes', 'Total Packets', 'Tx Drops', 'Rx Error'] 127 | for i in range(len(heading)): 128 | worksheet.write(row, column + i, heading[i]) 129 | row = 1 130 | column = 0 131 | portstats = response.json()['port_statistics_element'] 132 | for i in range(len(portstats)): 133 | stats = [portstats[i]['id'], 134 | (portstats[i]['bytes_rx'] + portstats[i]['bytes_tx']), 135 | (portstats[i]['packets_rx'] + portstats[i]['packets_tx']), 136 | portstats[i]['error_tx'], 137 | portstats[i]['error_rx'] 138 | ] 139 | 140 | print(stats) 141 | for x in range(len(stats)): 142 | worksheet.write(row, column, stats[x]) 143 | column += 1 144 | row = row + 1 145 | column = 0 146 | 147 | 148 | def conf_rate_limit(baseurl, cookie_header, ratelimitport): 149 | """ 150 | Configure rate-limit to the port 151 | :param baseurl: imported baseurl variable 152 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 153 | :param ratelimitport: imported from yaml file for the port id 154 | """ 155 | cmd1 = "configure terminal" 156 | cmd2 = "interface " + ratelimitport["port_id"] 157 | cmd3 = "rate-limit all in percent 1" 158 | cmd4 = "rate-limit all out percent 1" 159 | common.anycli(baseurl, cmd1, cookie_header) 160 | common.anycli(baseurl, cmd2, cookie_header) 161 | common.anycli(baseurl, cmd3, cookie_header) 162 | common.anycli(baseurl, cmd4, cookie_header) 163 | 164 | 165 | def monitor_port(baseurl, cookie_header, port): 166 | """ 167 | Monitor a specific port for its statistics. If the rate goes above 2000 rate limit gets applied 168 | :param baseurl: imported baseurl variable 169 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 170 | :return Prints on screen when port reaches a threshold . 171 | conf_rate_limit function is called to configure rate limit the port. 172 | """ 173 | url = baseurl + 'port-statistics/' + port['port_id'] 174 | headers = {'cookie': cookie_header} 175 | response = requests.get(url, verify=False, headers=headers) 176 | if response.status_code == 200: 177 | txpkts = response.json()['packets_tx'] 178 | print("Tx packets: {}".format(txpkts)) 179 | if txpkts > 2000: 180 | print("Configuring rate limit on port {} as the TX rate gone above the threshold".format(port['port_id'])) 181 | conf_rate_limit(baseurl, cookie_header, port) 182 | 183 | 184 | def get_mac_table(baseurl, cookie_header): 185 | """ 186 | Get all users in the switch 187 | :param baseurl: imported baseurl variable 188 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 189 | :return Returns all users in the switch - equivalent to 'show mac-address' in switch 190 | """ 191 | url = baseurl + 'mac-table' 192 | headers = {'cookie': cookie_header} 193 | response = requests.get(url, verify=False, headers=headers) 194 | if response.status_code == 200: 195 | return response.json() 196 | 197 | 198 | def port_update(baseurl, cookie_header, port): 199 | """ 200 | Configure a port admin status to go UP or Down 201 | :param baseurl: imported baseurl variable 202 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 203 | :param port: imported data from yaml file to update a port admin status 204 | :return Prints the status of port status in case of success update 205 | """ 206 | url = baseurl + 'ports/' + port['id'] 207 | headers = {'cookie': cookie_header} 208 | response = requests.put(url, verify=False, data=json.dumps(port), headers=headers) 209 | if response.status_code == 200: 210 | print("Port {} UP status updated as {}".format(port['id'], port['is_port_up'])) 211 | 212 | 213 | def get_transceivers(baseurl, cookie_header): 214 | """ 215 | Get all transceivers in the switch 216 | :param baseurl: imported baseurl variable 217 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 218 | :return return all transceivers information in json format 219 | """ 220 | url = baseurl + 'transceivers' 221 | headers = {'cookie': cookie_header} 222 | response = requests.get(url, verify=False, headers=headers) 223 | if response.status_code == 200: 224 | return response.json() 225 | 226 | 227 | def get_transceiver_detail(baseurl, cookie_header, transceiver): 228 | """ 229 | Get details of a given transceivers in the switch 230 | :param baseurl: imported baseurl variable 231 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 232 | :param transceiver: data parsed to specify a transceiver in switch 233 | :return return transceiver's detailed information in json format 234 | """ 235 | url = baseurl + 'transceivers/' + transceiver 236 | headers = {'cookie': cookie_header} 237 | response = requests.get(url, verify=False, headers=headers) 238 | if response.status_code == 200: 239 | return response.json() 240 | 241 | 242 | def get_transceiver_diagnostics(baseurl, cookie_header, transceiver): 243 | """ 244 | Get the diagnostics of a given transceivers in the switch 245 | :param baseurl: imported baseurl variable 246 | :param cookie_header: Parse cookie resulting from successful loginOS.login_os(baseurl) 247 | :param transceiver: data parsed to specify a transceiver in switch 248 | :return return transceiver's diagnostics information in json format 249 | """ 250 | url = baseurl + 'transceivers/' + transceiver + '/diagnostics' 251 | headers = {'cookie': cookie_header} 252 | response = requests.get(url, verify=False, headers=headers) 253 | if response.status_code == 200: 254 | return response.json() 255 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------