diff --git a/confluent_server/confluent/networking/lldp.py b/confluent_server/confluent/networking/lldp.py index 3ff04ebf..a4180764 100644 --- a/confluent_server/confluent/networking/lldp.py +++ b/confluent_server/confluent/networking/lldp.py @@ -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()) \ No newline at end of file diff --git a/confluent_server/confluent/networking/macmap.py b/confluent_server/confluent/networking/macmap.py index b364f832..06f18eef 100644 --- a/confluent_server/confluent/networking/macmap.py +++ b/confluent_server/confluent/networking/macmap.py @@ -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: diff --git a/confluent_server/confluent/networking/netutil.py b/confluent_server/confluent/networking/netutil.py new file mode 100644 index 00000000..fb140ef2 --- /dev/null +++ b/confluent_server/confluent/networking/netutil.py @@ -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 \ No newline at end of file