2
0
mirror of https://github.com/xcat2/confluent.git synced 2025-08-23 19:50:23 +00:00

Further refactor and mature neighbor data

At this point, the /networknig/neighbors mostly works...
This commit is contained in:
Jarrod Johnson
2017-09-27 14:24:00 -04:00
parent ea27125587
commit 317809f449
3 changed files with 150 additions and 89 deletions

View File

@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016 Lenovo
# Copyright 2016, 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -30,10 +30,18 @@
# Provides support for viewing and processing lldp data for switches
if __name__ == '__main__':
import sys
import confluent.config.configmanager as cfm
import confluent.exceptions as exc
import confluent.log as log
import confluent.messages as msg
import confluent.snmputil as snmp
import confluent.networking.netutil as netutil
import confluent.util as util
import eventlet
from eventlet.greenpool import GreenPool
import eventlet.semaphore
import re
# The interesting OIDs are:
@@ -70,6 +78,10 @@ import re
# # 1.0.8802.1.1.2.1.4.1.1.10 - SysDesc - good stuff
_neighdata = {}
_updatelocks = {}
def lenovoname(idx, desc):
if desc.isdigit():
return 'Ethernet' + str(idx)
@@ -104,23 +116,52 @@ def _extract_neighbor_data_b(args):
idxtoifname[idx] = _lldpdesc_to_ifname(sid, idx, str(oidindex[1]))
for remotedesc in conn.walk('1.0.8802.1.1.2.1.4.1.1.10'):
iname = idxtoifname[remotedesc[0][-2]]
lldpdata[iname] = {'description': str(remotedesc[1])}
lldpdata[iname] = {'peerdescription': str(remotedesc[1])}
for remotename in conn.walk('1.0.8802.1.1.2.1.4.1.1.9'):
iname = idxtoifname[remotename[0][-2]]
if iname not in lldpdata:
lldpdata[iname] = {}
lldpdata[iname]['name'] = str(remotename[1])
lldpdata[iname]['peername'] = str(remotename[1])
for remotename in conn.walk('1.0.8802.1.1.2.1.4.1.1.7'):
iname = idxtoifname[remotename[0][-2]]
if iname not in lldpdata:
lldpdata[iname] = {}
lldpdata[iname]['peerport'] = str(remotename[1])
for remoteid in conn.walk('1.0.8802.1.1.2.1.4.1.1.5'):
iname = idxtoifname[remoteid[0][-2]]
if iname not in lldpdata:
lldpdata[iname] = {}
lldpdata[iname]['chassisid'] = str(remoteid[1])
print(repr(lldpdata))
lldpdata[iname]['peerchassisid'] = str(remoteid[1])
_neighdata[switch] = lldpdata
def update_switch_data(switch, configmanager):
switchcreds = netutil.get_switchcreds(configmanager, (switch,))[0]
_extract_neighbor_data(switchcreds)
return _neighdata.get(switch, {})
def _update_neighbors_backend(configmanager):
global _neighdata
_neighdata = {}
switches = list_switches(configmanager)
switchcreds = netutil.get_switchcreds(configmanager, switches)
pool = GreenPool(64)
for ans in pool.imap(_extract_neighbor_data, switchcreds):
yield ans
def _extract_neighbor_data(args):
# single switch neighbor data update
switch = args[0]
if switch not in _updatelocks:
_updatelocks[switch] = eventlet.semaphore.Semaphore()
if _updatelocks[switch].locked():
while _updatelocks[switch].locked():
eventlet.sleep(1)
return
try:
_extract_neighbor_data_b(args)
with _updatelocks[switch]:
_extract_neighbor_data_b(args)
except Exception:
log.logtrace()
@@ -128,4 +169,26 @@ if __name__ == '__main__':
# a quick one-shot test, args are switch and snmpv1 string for now
# (should do three argument form for snmpv3 test
import sys
_extract_neighbor_data((sys.argv[1], sys.argv[2]))
_extract_neighbor_data((sys.argv[1], sys.argv[2], None))
print(repr(_neighdata))
def _handle_neighbor_query(pathcomponents, configmanager):
switchname = pathcomponents[0]
if len(pathcomponents) == 1:
return [msg.ChildCollection('by-port/')]
if len(pathcomponents) == 2:
# need to list ports for the switchname
update_switch_data(switchname, configmanager)
return [msg.ChildCollection(x) for x in util.natural_sort(
_neighdata[switchname])]
portname = pathcomponents[2]
return [msg.ChildCollection(repr(_neighdata[switchname][portname]))]
def _list_interfaces(switchname, configmanager):
switchcreds = get_switchcreds(configmanager, (switchname,))
switchcreds = switchcreds[0]
conn = snmp.Session(*switchcreds)
ifnames = netutil.get_portnamemap(conn)
return util.natural_sort(ifnames.values())

View File

@@ -30,11 +30,12 @@
# this module will provide mac to switch and full 'ifName' label
# This functionality is restricted to the null tenant
from confluent.networking.lldp import _handle_neighbor_query
from confluent.networking.netutil import get_switchcreds, list_switches
if __name__ == '__main__':
import sys
import confluent.config.configmanager as cfm
import confluent.networking.lldp as lldp
import confluent.exceptions as exc
import confluent.log as log
import confluent.messages as msg
@@ -181,7 +182,7 @@ def _map_switch_backend(args):
except ValueError:
# ifidx might be '', skip in such a case
continue
ifnamemap = _get_portnamemap(conn)
ifnamemap = netutil.get_portnamemap(conn)
maccounts = {}
bridgetoifvalid = False
for mac in mactobridge:
@@ -240,24 +241,6 @@ def _map_switch_backend(args):
_nodesbymac[mac] = nodename
def _get_portnamemap(conn):
ifnamemap = {}
havenames = False
for vb in conn.walk('1.3.6.1.2.1.31.1.1.1.1'):
ifidx, ifname = vb
if not ifname:
continue
havenames = True
ifidx = int(str(ifidx).rsplit('.', 1)[1])
ifnamemap[ifidx] = str(ifname)
if not havenames:
for vb in conn.walk('1.3.6.1.2.1.2.2.1.2'):
ifidx, ifname = vb
ifidx = int(str(ifidx).rsplit('.', 1)[1])
ifnamemap[ifidx] = str(ifname)
return ifnamemap
def find_node_by_mac(mac, configmanager):
now = util.monotonic_time()
if vintage and (now - vintage) < 90 and mac in _nodesbymac:
@@ -299,27 +282,12 @@ def update_macmap(configmanager, impatient=False):
eventlet.spawn_n(_finish_update, completions)
raise
def _finish_update(completions):
for _ in completions:
pass
def _list_switches(configmanager):
nodelocations = configmanager.get_node_attributes(
configmanager.list_nodes(), ('net*.switch', 'net*.switchport'))
switches = set([])
for node in nodelocations:
cfg = nodelocations[node]
for attr in cfg:
if not attr.endswith('.switch') or 'value' not in cfg[attr]:
continue
curswitch = cfg[attr].get('value', None)
if not curswitch:
continue
switches.add(curswitch)
return util.natural_sort(switches)
def _full_updatemacmap(configmanager):
global vintage
global _macmap
@@ -365,35 +333,13 @@ def _full_updatemacmap(configmanager):
_switchportmap[curswitch][portname] = None
else:
_switchportmap[curswitch][portname] = node
switchauth = _get_switchcreds(configmanager, switches)
pool = GreenPool()
switchauth = get_switchcreds(configmanager, switches)
pool = GreenPool(64)
for ans in pool.imap(_map_switch, switchauth):
vintage = util.monotonic_time()
yield ans
def _get_switchcreds(configmanager, switches):
switchcfg = configmanager.get_node_attributes(
switches, ('secret.hardwaremanagementuser', 'secret.snmpcommunity',
'secret.hardwaremanagementpassword'), decrypt=True)
switchauth = []
for switch in switches:
if not switch:
continue
switchparms = switchcfg.get(switch, {})
user = None
password = switchparms.get(
'secret.snmpcommunity', {}).get('value', None)
if not password:
password = switchparms.get(
'secret.hardwaremanagementpassword', {}).get('value',
'public')
user = switchparms.get(
'secret.hardwaremanagementuser', {}).get('value', None)
switchauth.append((switch, password, user))
return switchauth
def _dump_locations(info, macaddr, nodename=None):
yield msg.KeyValueData({'possiblenode': nodename, 'mac': macaddr})
retdata = {}
@@ -420,26 +366,6 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
operation, '/'.join(pathcomponents)))
def _list_interfaces(switchname, configmanager):
switchcreds = _get_switchcreds(configmanager, (switchname,))
switchcreds = switchcreds[0]
conn = snmp.Session(*switchcreds)
ifnames = _get_portnamemap(conn)
return util.natural_sort(ifnames.values())
def _handle_neighbor_query(pathcomponents, configmanager):
switchname = pathcomponents[0]
if len(pathcomponents) == 1:
return [msg.ChildCollection('by-port/')]
if len(pathcomponents) == 2:
# need to list ports for the switchname
return [msg.ChildCollection(x) for x in _list_interfaces(switchname,
configmanager)]
portname = pathcomponents[2]
return [msg.ChildCollection(portname)]
def handle_read_api_request(pathcomponents, configmanager):
# TODO(jjohnson2): discovery core.py api handler design, apply it here
# to make this a less tangled mess as it gets extended
@@ -451,7 +377,7 @@ def handle_read_api_request(pathcomponents, configmanager):
return [msg.ChildCollection('by-switch/')]
elif len(pathcomponents) == 3:
return [msg.ChildCollection(x + '/')
for x in _list_switches(configmanager)]
for x in list_switches(configmanager)]
else:
return _handle_neighbor_query(pathcomponents[3:], configmanager)
elif len(pathcomponents) == 2:
@@ -481,7 +407,7 @@ def handle_read_api_request(pathcomponents, configmanager):
elif pathcomponents[2] == 'by-switch':
if len(pathcomponents) == 3:
return [msg.ChildCollection(x + '/')
for x in _list_switches(configmanager)]
for x in list_switches(configmanager)]
if len(pathcomponents) == 4:
return [msg.ChildCollection('by-port/')]
if len(pathcomponents) == 5:

View File

@@ -0,0 +1,72 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import confluent.util as util
def get_switchcreds(configmanager, switches):
switchcfg = configmanager.get_node_attributes(
switches, ('secret.hardwaremanagementuser', 'secret.snmpcommunity',
'secret.hardwaremanagementpassword'), decrypt=True)
switchauth = []
for switch in switches:
if not switch:
continue
switchparms = switchcfg.get(switch, {})
user = None
password = switchparms.get(
'secret.snmpcommunity', {}).get('value', None)
if not password:
password = switchparms.get(
'secret.hardwaremanagementpassword', {}).get('value',
'public')
user = switchparms.get(
'secret.hardwaremanagementuser', {}).get('value', None)
switchauth.append((switch, password, user))
return switchauth
def list_switches(configmanager):
nodelocations = configmanager.get_node_attributes(
configmanager.list_nodes(), ('net*.switch', 'net*.switchport'))
switches = set([])
for node in nodelocations:
cfg = nodelocations[node]
for attr in cfg:
if not attr.endswith('.switch') or 'value' not in cfg[attr]:
continue
curswitch = cfg[attr].get('value', None)
if not curswitch:
continue
switches.add(curswitch)
return util.natural_sort(switches)
def get_portnamemap(conn):
ifnamemap = {}
havenames = False
for vb in conn.walk('1.3.6.1.2.1.31.1.1.1.1'):
ifidx, ifname = vb
if not ifname:
continue
havenames = True
ifidx = int(str(ifidx).rsplit('.', 1)[1])
ifnamemap[ifidx] = str(ifname)
if not havenames:
for vb in conn.walk('1.3.6.1.2.1.2.2.1.2'):
ifidx, ifname = vb
ifidx = int(str(ifidx).rsplit('.', 1)[1])
ifnamemap[ifidx] = str(ifname)
return ifnamemap