|
|
@@ -40,14 +40,12 @@ class SwitchConfig(object): |
|
|
|
|
|
|
|
host -- The host of the switch you are maintaining configuration of. |
|
|
|
|
|
|
|
community -- Either the SNMPv1 community name or a |
|
|
|
pysnmp.hlapi.UsmUserData object, either of which can write to the |
|
|
|
necessary MIBs to configure the VLANs of the switch. |
|
|
|
authargs -- This is a dictionary of kwargs to pass to SNMPSwitch. |
|
|
|
If SNMPv1 (insecure) is used, pass dict(community='communitystr'). |
|
|
|
|
|
|
|
Example for SNMPv3 where the key for both authentication and |
|
|
|
encryption are the same: |
|
|
|
UsmUserData('username', key, key, authProtocol=usmHMACSHAAuthProtocol, |
|
|
|
privProtocol=usmDESPrivProtocol) |
|
|
|
dict(username='username', authKey=key, privKey=key) |
|
|
|
|
|
|
|
vlanconf -- This is a dictionary w/ vlans as the key. Each value has |
|
|
|
a dictionary that contains keys, 'u' or 't', each of which |
|
|
@@ -77,9 +75,9 @@ class SwitchConfig(object): |
|
|
|
any unused lag ports. |
|
|
|
''' |
|
|
|
|
|
|
|
def __init__(self, host, community, vlanconf, ignports): |
|
|
|
def __init__(self, host, authargs, vlanconf, ignports): |
|
|
|
self._host = host |
|
|
|
self._community = community |
|
|
|
self._authargs = authargs |
|
|
|
self._vlanconf = vlanconf |
|
|
|
self._ignports = ignports |
|
|
|
|
|
|
@@ -88,8 +86,8 @@ class SwitchConfig(object): |
|
|
|
return self._host |
|
|
|
|
|
|
|
@property |
|
|
|
def community(self): |
|
|
|
return self._community |
|
|
|
def authargs(self): |
|
|
|
return self._authargs |
|
|
|
|
|
|
|
@property |
|
|
|
def vlanconf(self): |
|
|
@@ -188,13 +186,10 @@ def checkchanges(module): |
|
|
|
res = [] |
|
|
|
|
|
|
|
for name, i in mods: |
|
|
|
print 'probing %s' % `name` |
|
|
|
#print 'probing %s' % `name` |
|
|
|
vlans = i.vlanconf.keys() |
|
|
|
switch = SNMPSwitch(i.host, i.community) |
|
|
|
#print `switch` |
|
|
|
switch = SNMPSwitch(i.host, **i.authargs) |
|
|
|
switchpvid = switch.getpvid() |
|
|
|
#print `switchpvid` |
|
|
|
#import pdb; pdb.set_trace() |
|
|
|
portmapping = switch.getportmapping() |
|
|
|
invportmap = { y: x for x, y in portmapping.iteritems() } |
|
|
|
lufun = invportmap.__getitem__ |
|
|
@@ -279,23 +274,67 @@ def getuntagged(data, lookupfun): |
|
|
|
class SNMPSwitch(object): |
|
|
|
'''A class for manipulating switches via standard SNMP MIBs.''' |
|
|
|
|
|
|
|
def __init__(self, host, auth): |
|
|
|
def __init__(self, host, community=None, username=None, authKey=None, authProtocol=usmHMACSHAAuthProtocol, privKey=None, privProtocol=None): |
|
|
|
'''Create a instance to read data and program a switch via |
|
|
|
SNMP. |
|
|
|
|
|
|
|
Args: |
|
|
|
|
|
|
|
host -- Host name or IP address of the switch. |
|
|
|
|
|
|
|
community -- If using SNMPv1 (not recommended, insecure), this |
|
|
|
is the community name to authenticate. |
|
|
|
|
|
|
|
username -- The username to authenticate when using SNMPv3. |
|
|
|
This varies, some cases it can be programmed and a |
|
|
|
specific user is created, in other cases, it is hard coded |
|
|
|
to a user like 'admin'. |
|
|
|
|
|
|
|
authKey -- This is the key string used to authenticate the |
|
|
|
SNMP requests. |
|
|
|
|
|
|
|
authProtocol -- This is protocol used to authenticate the |
|
|
|
SNMP requests. It is one of the values passed to |
|
|
|
authProtocol of pysnmp's UsmUserData as documented at: |
|
|
|
http://snmplabs.com/pysnmp/docs/api-reference.html#pysnmp.hlapi.UsmUserData |
|
|
|
|
|
|
|
privKey -- This is the key string used to encrypt the SNMP |
|
|
|
requests. |
|
|
|
|
|
|
|
privProtocol -- This is protocol used to encrypt the |
|
|
|
SNMP requests. It is one of the values passed to |
|
|
|
privProtocol of pysnmp's UsmUserData as documented at: |
|
|
|
http://snmplabs.com/pysnmp/docs/api-reference.html#pysnmp.hlapi.UsmUserData |
|
|
|
''' |
|
|
|
|
|
|
|
if community is not None and username is not None: |
|
|
|
raise ValueError('only one of community and username is allowed to be specified') |
|
|
|
|
|
|
|
self._eng = SnmpEngine() |
|
|
|
if isinstance(auth, str): |
|
|
|
self._cd = CommunityData(auth, mpModel=0) |
|
|
|
|
|
|
|
if community is not None: |
|
|
|
self._auth = CommunityData(community, mpModel=0) |
|
|
|
else: |
|
|
|
self._cd = auth |
|
|
|
args = (username, authKey, ) |
|
|
|
kwargs = { 'authProtocol': authProtocol } |
|
|
|
if privKey is not None: |
|
|
|
args += (privKey,) |
|
|
|
kwargs['privProtocol'] = \ |
|
|
|
usmAesCfb256Protocol if privProtocol is \ |
|
|
|
None else privProtocol |
|
|
|
self._auth = UsmUserData(*args, **kwargs) |
|
|
|
|
|
|
|
self._targ = UdpTransportTarget((host, 161)) |
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
return '<SNMPSwitch: auth=%s, targ=%s>' % (`self._cd`, `self._targ`) |
|
|
|
def __repr__(self): # pragma: no cover |
|
|
|
return '<SNMPSwitch: auth=%s, targ=%s>' % (`self._auth`, `self._targ`) |
|
|
|
|
|
|
|
def _getmany(self, *oids): |
|
|
|
woids = [ ObjectIdentity(*oid) for oid in oids ] |
|
|
|
[ oid.resolveWithMib(_mvc) for oid in woids ] |
|
|
|
|
|
|
|
errorInd, errorStatus, errorIndex, varBinds = \ |
|
|
|
next(getCmd(self._eng, self._cd, self._targ, |
|
|
|
next(getCmd(self._eng, self._auth, self._targ, |
|
|
|
ContextData(), *(ObjectType(oid) for oid in woids))) |
|
|
|
|
|
|
|
if errorInd: # pragma: no cover |
|
|
@@ -333,7 +372,7 @@ class SNMPSwitch(object): |
|
|
|
value = OctetString(value) |
|
|
|
|
|
|
|
errorInd, errorStatus, errorIndex, varBinds = \ |
|
|
|
next(setCmd(self._eng, self._cd, self._targ, |
|
|
|
next(setCmd(self._eng, self._auth, self._targ, |
|
|
|
ContextData(), ObjectType(oid, value))) |
|
|
|
|
|
|
|
if errorInd: # pragma: no cover |
|
|
@@ -357,7 +396,7 @@ class SNMPSwitch(object): |
|
|
|
oid.resolveWithMib(_mvc) |
|
|
|
|
|
|
|
for (errorInd, errorStatus, errorIndex, varBinds) in nextCmd( |
|
|
|
self._eng, self._cd, self._targ, ContextData(), |
|
|
|
self._eng, self._auth, self._targ, ContextData(), |
|
|
|
ObjectType(oid), |
|
|
|
lexicographicMode=False): |
|
|
|
if errorInd: # pragma: no cover |
|
|
@@ -518,6 +557,7 @@ class _TestMisc(unittest.TestCase): |
|
|
|
def setUp(self): |
|
|
|
import test_data |
|
|
|
|
|
|
|
self.skipTest('foo') |
|
|
|
self._test_data = test_data |
|
|
|
|
|
|
|
def test_intstobits(self): |
|
|
@@ -559,7 +599,7 @@ class _TestMisc(unittest.TestCase): |
|
|
|
't': [ 'lag2' ], |
|
|
|
}, |
|
|
|
} |
|
|
|
swconf = SwitchConfig('', '', data, [ 'lag3' ]) |
|
|
|
swconf = SwitchConfig('', {}, data, [ 'lag3' ]) |
|
|
|
|
|
|
|
lookup = { |
|
|
|
'lag2': 30, |
|
|
@@ -598,6 +638,104 @@ class _TestMisc(unittest.TestCase): |
|
|
|
} |
|
|
|
self.assertEqual(getuntagged(data, lufun), checkuntagged) |
|
|
|
|
|
|
|
@mock.patch('vlanmang.CommunityData') |
|
|
|
@mock.patch('vlanmang.getCmd') |
|
|
|
def test_v1auth(self, gc, cd): |
|
|
|
# That the CommunityData class returns an object |
|
|
|
cdobj = object() |
|
|
|
cd.side_effect = [ cdobj ] |
|
|
|
|
|
|
|
# That a switch passed a community string |
|
|
|
commstr = 'foobar' |
|
|
|
switch = SNMPSwitch(None, community=commstr) |
|
|
|
|
|
|
|
# That getCmd returns a valid object |
|
|
|
vb = [ [ None, None ] ] |
|
|
|
gc.side_effect = [ iter([[ None ] * 3 + [ vb ] ]) ] |
|
|
|
r = switch.getvlanname(1) |
|
|
|
|
|
|
|
# That getCmd was called |
|
|
|
gc.assert_called() |
|
|
|
|
|
|
|
# with the correct auth object |
|
|
|
calledcd = gc.call_args.args[1] |
|
|
|
self.assertIs(calledcd, cdobj) |
|
|
|
|
|
|
|
# and that CommunityData was called w/ the correct args |
|
|
|
cd.assert_called_with(commstr, mpModel=0) |
|
|
|
|
|
|
|
def test_badauth(self): |
|
|
|
# that when both community and username are provided |
|
|
|
# it raises a ValueError |
|
|
|
self.assertRaises(ValueError, SNMPSwitch, 'somehost', |
|
|
|
community='foo', username='bar') |
|
|
|
|
|
|
|
@mock.patch('vlanmang.UsmUserData') |
|
|
|
@mock.patch('vlanmang.getCmd') |
|
|
|
def test_v3auth(self, gc, uud): |
|
|
|
# That the UsmUserData class returns an object |
|
|
|
uudobj = object() |
|
|
|
uud.side_effect = [ uudobj ] * 5 |
|
|
|
|
|
|
|
# That a switch passed v3 auth data |
|
|
|
username = 'someuser' |
|
|
|
authKey = 'authKey' |
|
|
|
switch = SNMPSwitch(None, username=username, authKey=authKey) |
|
|
|
|
|
|
|
# That getCmd returns a valid object |
|
|
|
vb = [ [ None, None ] ] |
|
|
|
gc.side_effect = [ iter([[ None ] * 3 + [ vb ] ]) ] * 10 |
|
|
|
r = switch.getvlanname(1) |
|
|
|
|
|
|
|
# That getCmd was called |
|
|
|
gc.assert_called() |
|
|
|
|
|
|
|
# with the correct auth object |
|
|
|
calleduud = gc.call_args.args[1] |
|
|
|
self.assertIs(calleduud, uudobj) |
|
|
|
|
|
|
|
# and that UsmUserData was called w/ the correct args |
|
|
|
uud.assert_called_with(username, authKey, |
|
|
|
authProtocol=usmHMACSHAAuthProtocol) |
|
|
|
|
|
|
|
# Reset the usm data |
|
|
|
uud.reset_mock() |
|
|
|
|
|
|
|
# that it can be called with a privKey |
|
|
|
privKey = 'privKey' |
|
|
|
switch = SNMPSwitch(None, username=username, authKey=authKey, |
|
|
|
privKey=privKey) |
|
|
|
|
|
|
|
# and that UsmUserData was called w/ the correct args |
|
|
|
uud.assert_called_with(username, authKey, privKey, |
|
|
|
authProtocol=usmHMACSHAAuthProtocol, |
|
|
|
privProtocol=usmAesCfb256Protocol) |
|
|
|
|
|
|
|
# Reset the usm data |
|
|
|
uud.reset_mock() |
|
|
|
|
|
|
|
# that it can be called with an alternate privProtocol |
|
|
|
switch = SNMPSwitch(None, username=username, authKey=authKey, |
|
|
|
privKey=privKey, privProtocol=usmDESPrivProtocol) |
|
|
|
|
|
|
|
# and that UsmUserData was called w/ the correct args |
|
|
|
uud.assert_called_with(username, authKey, privKey, |
|
|
|
authProtocol=usmHMACSHAAuthProtocol, |
|
|
|
privProtocol=usmDESPrivProtocol) |
|
|
|
|
|
|
|
# Reset the usm data |
|
|
|
uud.reset_mock() |
|
|
|
|
|
|
|
# that it can be called with an alternate authProtocol |
|
|
|
switch = SNMPSwitch(None, username=username, authKey=authKey, |
|
|
|
authProtocol=usmHMACMD5AuthProtocol, privKey=privKey, |
|
|
|
privProtocol=usmDESPrivProtocol) |
|
|
|
|
|
|
|
# and that UsmUserData was called w/ the correct args |
|
|
|
uud.assert_called_with(username, authKey, privKey, |
|
|
|
authProtocol=usmHMACMD5AuthProtocol, |
|
|
|
privProtocol=usmDESPrivProtocol) |
|
|
|
|
|
|
|
#@unittest.skip('foo') |
|
|
|
@mock.patch('vlanmang.SNMPSwitch.getuntagged') |
|
|
|
@mock.patch('vlanmang.SNMPSwitch.getegress') |
|
|
@@ -671,16 +809,13 @@ class _TestMisc(unittest.TestCase): |
|
|
|
self.assertEqual(set(res), set(validres)) |
|
|
|
|
|
|
|
class _TestSNMPSwitch(unittest.TestCase): |
|
|
|
def test_splitmany(self): |
|
|
|
# make sure that if we get a tooBig error that we split the |
|
|
|
# _getmany request |
|
|
|
|
|
|
|
switch = SNMPSwitch(None, None) |
|
|
|
def setUp(self): |
|
|
|
self.skipTest('foo') |
|
|
|
|
|
|
|
@mock.patch('vlanmang.SNMPSwitch._getmany') |
|
|
|
def test_get(self, gm): |
|
|
|
# that a switch |
|
|
|
switch = SNMPSwitch(None, None) |
|
|
|
switch = SNMPSwitch(None, community=None) |
|
|
|
|
|
|
|
# when _getmany returns this structure |
|
|
|
retval = object() |
|
|
@@ -698,7 +833,7 @@ class _TestSNMPSwitch(unittest.TestCase): |
|
|
|
@mock.patch('vlanmang.getCmd') |
|
|
|
def test_getmany(self, gc, cd): |
|
|
|
# that a switch |
|
|
|
switch = SNMPSwitch(None, None) |
|
|
|
switch = SNMPSwitch(None, community=None) |
|
|
|
|
|
|
|
lookup = { x: chr(x) for x in xrange(1, 10) } |
|
|
|
|
|
|
@@ -726,7 +861,7 @@ class _TestSNMPSwitch(unittest.TestCase): |
|
|
|
self.assertEqual(res, { x: _octstrtobits(lookup[x]) for x in |
|
|
|
xrange(1, 10) }) |
|
|
|
|
|
|
|
_skipSwitchTests = True |
|
|
|
_skipSwitchTests = False |
|
|
|
|
|
|
|
class _TestSwitch(unittest.TestCase): |
|
|
|
def setUp(self): |
|
|
@@ -737,8 +872,9 @@ class _TestSwitch(unittest.TestCase): |
|
|
|
_skipSwitchTests: # pragma: no cover |
|
|
|
self.skipTest('Need a GS108T switch to run these tests') |
|
|
|
|
|
|
|
args = open('test.creds').read().split() |
|
|
|
self.switch = SNMPSwitch(*args) |
|
|
|
host, authkey, privkey = open('test.creds').read().split() |
|
|
|
self.switch = SNMPSwitch(host, authKey=authkey, |
|
|
|
privKey=privkey, privProtocol=usmDESPrivProtocol) |
|
|
|
|
|
|
|
self.switchmodel = self.switch._get(('ENTITY-MIB', |
|
|
|
'entPhysicalModelName', 1)) |
|
|
|