Skip to content

Commit

Permalink
Use SNMPv2 instead of SSH2 for communication with the switch
Browse files Browse the repository at this point in the history
  • Loading branch information
leonhandreke committed Aug 8, 2012
1 parent 974d873 commit 7b207b1
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 347 deletions.
8 changes: 2 additions & 6 deletions README.md
Expand Up @@ -2,20 +2,16 @@

`hpswitch` is a Python module to allow interaction with HP Networking switches in a pythonic fashion.

It uses [paramiko](http://www.lag.net/paramiko/) to interact with the switch using the SSH2 protocol. Its goal is to be a more or less direct interface to the switch, without any additional abstraction layers.
It uses [pySNMP](http://pysnmp.sourceforge.net/) to interact with the switch using the SNMPv2 protocol.

`hpswitch` also depends on the `ipaddress` module. The functionality of this module is outlined in [PEP 3144](http://www.python.org/dev/peps/pep-3144/). A reference implementation is [provided by Google](http://code.google.com/p/ipaddr-py). If you don't care and you just want to get up and running quickly, the file to `wget` is `http://hg.python.org/cpython/raw-file/tip/Lib/ipaddress.py`.

## Status

Currently, only a very small subset of the functionality of the switch CLI is implemented, mostly related to dealing with IP addressing, VLANs and interfaces.
Currently, only a very small subset of the functionality of the SNMP API is implemented, mostly related to dealing with IP addressing, VLANs and interfaces.

`hpswitch` *should* be compatible with the 3500, 3500yl, 5400zl, 6200yl, 6600 and 8200zl switch series. However, it has so far only been tested with a 5406zl switch. If you can confirm compatibility with other switch models, please let me know!

## Known Issues

Interfaces aggregated to form a trunk are not supported. Crashes will occur when trying to modify these interfaces.

## Documentation

Comments in `hpswitch` use the [Markdown](http://daringfireball.net/projects/markdown/) markup language. Pretty-looking and readable HTML can be generated using [pycco](http://fitzgen.github.com/pycco/).
Expand Down
93 changes: 19 additions & 74 deletions hpswitch/interface.py
@@ -1,7 +1,6 @@
import re
import string
# -*- coding: utf-8 -*-

import vlan
from pysnmp.proto import rfc1902

class Interface(object):
"""
Expand All @@ -14,32 +13,12 @@ def __init__(self, switch, identifier):
self.switch = switch
self.identifier = identifier

def _get_running_config_output(self):
"""
Get the output of the `show running-config interface [identifier]` command for this interface.
"""
run_output = self.switch.execute_command("show running-config interface " + str(self.identifier))

# If an error was returned by the switch, raise an Exception.
if "Module not present for port or invalid port" in run_output:
raise Exception("Invalid interface identifier.")
return run_output
ifindex = property(lambda self: self.switch._get_ifindex_for_interface_identifier(self.identifier))

def _get_name(self):
"""
Get the friendly name configured for this interface.
"""
run_output = self._get_running_config_output()
# Try to extract the interface name, which may also contain spaces. This is achieved by greedily matching
# whitespace at the end of the line and matching the ` name ` prefix a the beginning and using whatever
# remains of the string as the interface name. The `name` group is matched in a non-greedy fashion as to not
# "eat up" all the following whitespace which is not part of the name.
name_match = re.search(r"^ name \"(?P<name>.*?)\"\s*$", run_output, re.MULTILINE)
# Only return the name of the interface if a name was found.
if name_match:
return name_match.group('name')
# If no name was configured, return None.
return None

def _set_name(self, value):
"""
Expand All @@ -49,87 +28,53 @@ def _set_name(self, value):
# Management and Configuration Guide
assert(all(map(lambda letter: letter in (string.ascii_letters + string.digits), value)))
# Issue the commands on the switch to set the new name.
self.switch.execute_command("config")
self.switch.execute_command("interface {0} name {1}".format(self.identifier, value))
self.switch.execute_command("exit")
pass

def _del_name(self):
"""
Deconfigure the friendly name on this interface.
"""
self.switch.execute_command("config")
self.switch.execute_command("no interface {0} name".format(self.identifier))
self.switch.execute_command("exit")
pass

name = property(_get_name, _set_name, _del_name)

def _get_enabled(self):
"""
Get the admin status of this interface.
"""
run_output = self._get_running_config_output()
disable_match = re.search(r"^ disable\s*$", run_output, re.MULTILINE)
# If nothing hinting at a disabled port has been matched, return True, else False.
return disable_match is None
ifAdminStatus = self.switch.snmp_get(("ifAdminStatus", self.ifindex))
return int(ifAdminStatus) == 1

def _set_enabled(self, value):
"""
Set the admin status of this interface.
"""
self.switch.execute_command("config")
if value:
self.switch.execute_command("interface {0} enable".format(self.identifier))
else:
self.switch.execute_command("interface {0} disable".format(self.identifier))
self.switch.execute_command("exit")
self.switch.snmp_set(("ifAdminStatus", self.ifindex), rfc1902.Integer(1 if value else 2))

enabled = property(_get_enabled, _set_enabled)

def _get_operational(self):
"""
Get the operational status of this interface.
"""
ifOperStatus = self.switch.snmp_get(("ifOperStatus", self.ifindex))
return int(ifOperStatus) == 1

operational = property(_get_operational)

def _get_untagged_vlan(self):
"""
Get the untagged VLAN configured on this interface.
None is returned in case no VLAN is configured as untagged on this interface.
"""
run_output = self._get_running_config_output()
untagged_match = re.search(r"^ untagged vlan (?P<vid>\d{1,4})\s*$", run_output, re.MULTILINE)

if untagged_match:
return vlan.VLAN(self.switch, int(untagged_match.group('vid')))
return None
pass

untagged_vlan = property(_get_untagged_vlan)

def _get_tagged_vlans(self):
"""
Get a list of the tagged VLANs configured on this interface.
"""
tagged_vlans = []

run_output = self._get_running_config_output()

# To extract the complicated string describing the tagged VLANs, greedily match whitespace at the end and take
# the rest as the result.
tagged_vlan_match = re.search(r"^ tagged vlan (?P<vlan_list_string>.*?)\s*$", run_output, re.MULTILINE)
if tagged_vlan_match:
# VLAN range descriptors in running-config are seperated by commas, so seperate them.
vlan_range_strings = tagged_vlan_match.group('vlan_list_string').split(',')
# Resolve each VLAN range individually.
for vlan_range_string in vlan_range_strings:
# If this is a range, the first and the last VLAN in the range are seperated by a hyphen. If the item is
# only a single VLAN, splitting will do nothing.
vlan_range_components = vlan_range_string.split('-')
# Check if the current string describes a single VLAN.
if len(vlan_range_components) is 1:
# Create and remember a VLAN object with the single VLAN that was found
tagged_vlans.append(vlan.VLAN(self.switch, int(vlan_range_components[0])))
elif len(vlan_range_components) is 2:
# Iterate over the whole range given by the range string.
for vid_in_range in range(int(vlan_range_components[0]), int(vlan_range_components[1]) + 1):
# Append each VLAN in the range to the list of tagged VLANs.
tagged_vlans.append(vlan.VLAN(self.switch, vid_in_range))
else:
raise Exception("Invalid VLAN range format encountered.")

return tagged_vlans
pass

tagged_vlans = property(_get_tagged_vlans)

0 comments on commit 7b207b1

Please sign in to comment.