From 19c47e007cc0b8af7d8a0d4f2ef839f9caa000e1 Mon Sep 17 00:00:00 2001 From: ogenstad Date: Mon, 3 Nov 2014 21:44:45 +0100 Subject: [PATCH 1/3] Snmp module for Ansible --- net_infrastructure/snmp_facts.py | 365 +++++++++++++++++++++++++++++++ 1 file changed, 365 insertions(+) create mode 100755 net_infrastructure/snmp_facts.py diff --git a/net_infrastructure/snmp_facts.py b/net_infrastructure/snmp_facts.py new file mode 100755 index 00000000000..3f54569d018 --- /dev/null +++ b/net_infrastructure/snmp_facts.py @@ -0,0 +1,365 @@ +#!/usr/bin/python + +# This file is part of Networklore's snmp library for Ansible +# +# The module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The module is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + + +DOCUMENTATION = ''' +--- +module: snmp_facts +author: Patrick Ogenstad (@networklore) +notes: + - Version 0.7 +short_description: Retrive facts for a device using SNMP. +description: + - Retrieve facts for a device using SNMP, the facts will be + inserted to the ansible_facts key. +requirements: + - pysnmp +options: + host: + description: + - Set to {{ inventory_hostname }}} + required: true + version: + description: + - SNMP Version to use, v2/v2c or v3 + choices: [ 'v2', 'v2c', 'v3' ] + required: true + community: + description: + - The SNMP community string, required if version is v2/v2c + required: false + level: + description: + - Authentication level, required if version is v3 + choices: [ 'authPriv', 'authNoPriv' ] + required: false + username: + description: + - Username for SNMPv3, required if version is v3 + required: false + integrity: + description: + - Hashing algoritm, required if version is v3 + choices: [ 'md5', 'sha' ] + required: false + authkey: + description: + - Authentication key, required if version is v3 + required: false + privacy: + description: + - Encryption algoritm, required if level is authPriv + choices: [ 'des', 'aes' ] + required: false + privkey: + description: + - Encryption key, required if version is authPriv + required: false +''' + +EXAMPLES = ''' +# Gather facts with SNMP version 2 +- snmp_facts: host={{ inventory_hostname }} version=2c community=public + +# Gather facts using SNMP version 3 +- snmp_facts: + host={{ inventory_hostname }} + version=v3 + level=authPriv + integrity=sha + privacy=aes + username=snmp-user + authkey=abc12345 + privkey=def6789 +''' + +from ansible.module_utils.basic import * +from collections import defaultdict + +try: + from pysnmp.entity.rfc3413.oneliner import cmdgen + has_pysnmp = True +except: + has_pysnmp = False + +class DefineOid(object): + + def __init__(self,dotprefix=False): + if dotprefix: + dp = "." + else: + dp = "" + + # From SNMPv2-MIB + self.sysDescr = dp + "1.3.6.1.2.1.1.1.0" + self.sysObjectId = dp + "1.3.6.1.2.1.1.2.0" + self.sysUpTime = dp + "1.3.6.1.2.1.1.3.0" + self.sysContact = dp + "1.3.6.1.2.1.1.4.0" + self.sysName = dp + "1.3.6.1.2.1.1.5.0" + self.sysLocation = dp + "1.3.6.1.2.1.1.6.0" + + # From IF-MIB + self.ifIndex = dp + "1.3.6.1.2.1.2.2.1.1" + self.ifDescr = dp + "1.3.6.1.2.1.2.2.1.2" + self.ifMtu = dp + "1.3.6.1.2.1.2.2.1.4" + self.ifSpeed = dp + "1.3.6.1.2.1.2.2.1.5" + self.ifPhysAddress = dp + "1.3.6.1.2.1.2.2.1.6" + self.ifAdminStatus = dp + "1.3.6.1.2.1.2.2.1.7" + self.ifOperStatus = dp + "1.3.6.1.2.1.2.2.1.8" + self.ifAlias = dp + "1.3.6.1.2.1.31.1.1.1.18" + + # From IP-MIB + self.ipAdEntAddr = dp + "1.3.6.1.2.1.4.20.1.1" + self.ipAdEntIfIndex = dp + "1.3.6.1.2.1.4.20.1.2" + self.ipAdEntNetMask = dp + "1.3.6.1.2.1.4.20.1.3" + + +def decode_hex(hexstring): + + if len(hexstring) < 3: + return hexstring + if hexstring[:2] == "0x": + return hexstring[2:].decode("hex") + else: + return hexstring + +def decode_mac(hexstring): + + if len(hexstring) != 14: + return hexstring + if hexstring[:2] == "0x": + return hexstring[2:] + else: + return hexstring + +def lookup_adminstatus(int_adminstatus): + adminstatus_options = { + 1: 'up', + 2: 'down', + 3: 'testing' + } + if int_adminstatus in adminstatus_options.keys(): + return adminstatus_options[int_adminstatus] + else: + return "" + +def lookup_operstatus(int_operstatus): + operstatus_options = { + 1: 'up', + 2: 'down', + 3: 'testing', + 4: 'unknown', + 5: 'dormant', + 6: 'notPresent', + 7: 'lowerLayerDown' + } + if int_operstatus in operstatus_options.keys(): + return operstatus_options[int_operstatus] + else: + return "" + +def main(): + module = AnsibleModule( + argument_spec=dict( + host=dict(required=True), + version=dict(required=True, choices=['v2', 'v2c', 'v3']), + community=dict(required=False, default=False), + username=dict(required=False), + level=dict(required=False, choices=['authNoPriv', 'authPriv']), + integrity=dict(required=False, choices=['md5', 'sha']), + privacy=dict(required=False, choices=['des', 'aes']), + authkey=dict(required=False), + privkey=dict(required=False), + removeplaceholder=dict(required=False)), + required_together = ( ['username','level','integrity','authkey'],['privacy','privkey'],), + supports_check_mode=False) + + m_args = module.params + + if not has_pysnmp: + module.fail_json(msg='Missing required pysnmp module (check docs)') + + cmdGen = cmdgen.CommandGenerator() + + # Verify that we receive a community when using snmp v2 + if m_args['version'] == "v2" or m_args['version'] == "v2c": + if m_args['community'] == False: + module.fail_json(msg='Community not set when using snmp version 2') + + if m_args['version'] == "v3": + if m_args['username'] == None: + module.fail_json(msg='Username not set when using snmp version 3') + + if m_args['level'] == "authPriv" and m_args['privacy'] == None: + module.fail_json(msg='Privacy algorithm not set when using authPriv') + + + if m_args['integrity'] == "sha": + integrity_proto = cmdgen.usmHMACSHAAuthProtocol + elif m_args['integrity'] == "md5": + integrity_proto = cmdgen.usmHMACMD5AuthProtocol + + if m_args['privacy'] == "aes": + privacy_proto = cmdgen.usmAesCfb128Protocol + elif m_args['privacy'] == "des": + privacy_proto = cmdgen.usmDESPrivProtocol + + # Use SNMP Version 2 + if m_args['version'] == "v2" or m_args['version'] == "v2c": + snmp_auth = cmdgen.CommunityData(m_args['community']) + + # Use SNMP Version 3 with authNoPriv + elif m_args['level'] == "authNoPriv": + snmp_auth = cmdgen.UsmUserData(m_args['username'], authKey=m_args['authkey'], authProtocol=integrity_proto) + + # Use SNMP Version 3 with authPriv + else: + snmp_auth = cmdgen.UsmUserData(m_args['username'], authKey=m_args['authkey'], privKey=m_args['privkey'], authProtocol=integrity_proto, privProtocol=privacy_proto) + + # Use p to prefix OIDs with a dot for polling + p = DefineOid(dotprefix=True) + # Use v without a prefix to use with return values + v = DefineOid(dotprefix=False) + + Tree = lambda: defaultdict(Tree) + + results = Tree() + + errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd( + snmp_auth, + cmdgen.UdpTransportTarget((m_args['host'], 161)), + cmdgen.MibVariable(p.sysDescr,), + cmdgen.MibVariable(p.sysObjectId,), + cmdgen.MibVariable(p.sysUpTime,), + cmdgen.MibVariable(p.sysContact,), + cmdgen.MibVariable(p.sysName,), + cmdgen.MibVariable(p.sysLocation,), + ) + + + if errorIndication: + module.fail_json(msg=str(errorIndication)) + + for oid, val in varBinds: + current_oid = oid.prettyPrint() + current_val = val.prettyPrint() + if current_oid == v.sysDescr: + results['ansible_sysdescr'] = decode_hex(current_val) + elif current_oid == v.sysObjectId: + results['ansible_sysobjectid'] = current_val + elif current_oid == v.sysUpTime: + results['ansible_sysuptime'] = current_val + elif current_oid == v.sysContact: + results['ansible_syscontact'] = current_val + elif current_oid == v.sysName: + results['ansible_sysname'] = current_val + elif current_oid == v.sysLocation: + results['ansible_syslocation'] = current_val + + errorIndication, errorStatus, errorIndex, varTable = cmdGen.nextCmd( + snmp_auth, + cmdgen.UdpTransportTarget((m_args['host'], 161)), + cmdgen.MibVariable(p.ifIndex,), + cmdgen.MibVariable(p.ifDescr,), + cmdgen.MibVariable(p.ifMtu,), + cmdgen.MibVariable(p.ifSpeed,), + cmdgen.MibVariable(p.ifPhysAddress,), + cmdgen.MibVariable(p.ifAdminStatus,), + cmdgen.MibVariable(p.ifOperStatus,), + cmdgen.MibVariable(p.ipAdEntAddr,), + cmdgen.MibVariable(p.ipAdEntIfIndex,), + cmdgen.MibVariable(p.ipAdEntNetMask,), + + cmdgen.MibVariable(p.ifAlias,), + ) + + + if errorIndication: + module.fail_json(msg=str(errorIndication)) + + interface_indexes = [] + + all_ipv4_addresses = [] + ipv4_networks = Tree() + + for varBinds in varTable: + for oid, val in varBinds: + current_oid = oid.prettyPrint() + current_val = val.prettyPrint() + if v.ifIndex in current_oid: + ifIndex = int(current_oid.rsplit('.', 1)[-1]) + results['ansible_interfaces'][ifIndex]['ifindex'] = current_val + interface_indexes.append(ifIndex) + if v.ifDescr in current_oid: + ifIndex = int(current_oid.rsplit('.', 1)[-1]) + results['ansible_interfaces'][ifIndex]['name'] = current_val + if v.ifMtu in current_oid: + ifIndex = int(current_oid.rsplit('.', 1)[-1]) + results['ansible_interfaces'][ifIndex]['mtu'] = current_val + if v.ifMtu in current_oid: + ifIndex = int(current_oid.rsplit('.', 1)[-1]) + results['ansible_interfaces'][ifIndex]['speed'] = current_val + if v.ifPhysAddress in current_oid: + ifIndex = int(current_oid.rsplit('.', 1)[-1]) + results['ansible_interfaces'][ifIndex]['mac'] = decode_mac(current_val) + if v.ifAdminStatus in current_oid: + ifIndex = int(current_oid.rsplit('.', 1)[-1]) + results['ansible_interfaces'][ifIndex]['adminstatus'] = lookup_adminstatus(int(current_val)) + if v.ifOperStatus in current_oid: + ifIndex = int(current_oid.rsplit('.', 1)[-1]) + results['ansible_interfaces'][ifIndex]['operstatus'] = lookup_operstatus(int(current_val)) + if v.ipAdEntAddr in current_oid: + curIPList = current_oid.rsplit('.', 4)[-4:] + curIP = ".".join(curIPList) + ipv4_networks[curIP]['address'] = current_val + all_ipv4_addresses.append(current_val) + if v.ipAdEntIfIndex in current_oid: + curIPList = current_oid.rsplit('.', 4)[-4:] + curIP = ".".join(curIPList) + ipv4_networks[curIP]['interface'] = current_val + if v.ipAdEntNetMask in current_oid: + curIPList = current_oid.rsplit('.', 4)[-4:] + curIP = ".".join(curIPList) + ipv4_networks[curIP]['netmask'] = current_val + + if v.ifAlias in current_oid: + ifIndex = int(current_oid.rsplit('.', 1)[-1]) + results['ansible_interfaces'][ifIndex]['description'] = current_val + + interface_to_ipv4 = {} + for ipv4_network in ipv4_networks: + current_interface = ipv4_networks[ipv4_network]['interface'] + current_network = { + 'address': ipv4_networks[ipv4_network]['address'], + 'netmask': ipv4_networks[ipv4_network]['netmask'] + } + if not current_interface in interface_to_ipv4: + interface_to_ipv4[current_interface] = [] + interface_to_ipv4[current_interface].append(current_network) + else: + interface_to_ipv4[current_interface].append(current_network) + + for interface in interface_to_ipv4: + results['ansible_interfaces'][int(interface)]['ipv4'] = interface_to_ipv4[interface] + + results['ansible_all_ipv4_addresses'] = all_ipv4_addresses + + module.exit_json(ansible_facts=results) + + +main() + From d4069bbc71bbd85f99b1e3c23d6d6bd09da3b82c Mon Sep 17 00:00:00 2001 From: ogenstad Date: Mon, 1 Dec 2014 19:34:39 +0100 Subject: [PATCH 2/3] added_version -> 1.9 and file move --- {net_infrastructure => network}/snmp_facts.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {net_infrastructure => network}/snmp_facts.py (100%) diff --git a/net_infrastructure/snmp_facts.py b/network/snmp_facts.py similarity index 100% rename from net_infrastructure/snmp_facts.py rename to network/snmp_facts.py From 24822322d4d3f9a8930e81bb93c18c85c398e422 Mon Sep 17 00:00:00 2001 From: ogenstad Date: Mon, 1 Dec 2014 19:39:49 +0100 Subject: [PATCH 3/3] 1.9 --- network/snmp_facts.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/network/snmp_facts.py b/network/snmp_facts.py index 3f54569d018..4d2d240ff28 100755 --- a/network/snmp_facts.py +++ b/network/snmp_facts.py @@ -19,9 +19,8 @@ DOCUMENTATION = ''' --- module: snmp_facts +version_added: "1.9" author: Patrick Ogenstad (@networklore) -notes: - - Version 0.7 short_description: Retrive facts for a device using SNMP. description: - Retrieve facts for a device using SNMP, the facts will be