|
1 #!/usr/bin/env python |
|
2 |
|
3 import pvl.args |
|
4 import pvl.hosts |
|
5 from pvl.snmp import snmp, lldp, vlan, bridge |
|
6 |
|
7 import collections |
|
8 import logging; log = logging.getLogger('pvl.hosts-lldp') |
|
9 import optparse |
|
10 |
|
11 def hosts_snmp (options, hosts) : |
|
12 """ |
|
13 Discover SNMP-supporting hosts. |
|
14 |
|
15 Yields Host, snmpdata |
|
16 """ |
|
17 |
|
18 for host in hosts : |
|
19 host_snmp = host.extensions.get('snmp') |
|
20 |
|
21 if not host_snmp : |
|
22 log.debug("%s: skip non-snmp host", host) |
|
23 continue |
|
24 |
|
25 elif host.down : |
|
26 log.debug("%s: skip down host", host) |
|
27 continue |
|
28 |
|
29 else : |
|
30 log.debug("%s: %s", host, host_snmp) |
|
31 |
|
32 yield host, host_snmp |
|
33 |
|
34 def hosts_lldp (options, hosts) : |
|
35 """ |
|
36 Discover LLDP-supporting hosts. |
|
37 |
|
38 Yields Host, LLDPAgent |
|
39 """ |
|
40 |
|
41 for host, host_snmp in hosts_snmp(options, hosts) : |
|
42 agent = lldp.LLDPAgent.apply(options, host.fqdn(), community=host_snmp.get('community')) |
|
43 |
|
44 try : |
|
45 local = agent.local |
|
46 except snmp.SNMPError as ex : |
|
47 log.warning("%s: %s", host, ex) |
|
48 continue |
|
49 |
|
50 if not local : |
|
51 log.info("%s: no lldp support", host) |
|
52 continue |
|
53 |
|
54 log.info("%s: %s", host, local) |
|
55 |
|
56 if local['sys_name'] != host.host : |
|
57 log.warning("%s: SNMP sys_name mismatch: %s", host, local['sys_name']) |
|
58 |
|
59 yield host, agent |
|
60 |
|
61 def hosts_vlan (options, hosts) : |
|
62 """ |
|
63 Discover VLAN-supporting hosts. |
|
64 |
|
65 Yields Host, VLANAgent |
|
66 """ |
|
67 |
|
68 for host, host_snmp in hosts_snmp(options, hosts) : |
|
69 agent = vlan.VLANAgent.apply(options, host.fqdn(), community=host_snmp.get('community')) |
|
70 |
|
71 try : |
|
72 count = agent.count |
|
73 except snmp.SNMPError as ex : |
|
74 log.warning("%s: %s", host, ex) |
|
75 continue |
|
76 |
|
77 log.info("%s: %s", host, count) |
|
78 |
|
79 yield host, agent |
|
80 |
|
81 def hosts_bridge (options, hosts) : |
|
82 """ |
|
83 Discover Bridge-supporting hosts. |
|
84 |
|
85 Yields Host, BridgeAgent |
|
86 """ |
|
87 |
|
88 for host, host_snmp in hosts_snmp(options, hosts) : |
|
89 agent = bridge.BridgeAgent.apply(options, host.fqdn(), community=host_snmp.get('community')) |
|
90 |
|
91 try : |
|
92 agent.ping() |
|
93 except snmp.SNMPError as ex : |
|
94 log.warning("%s: %s", host, ex) |
|
95 continue |
|
96 |
|
97 log.info("%s", host) |
|
98 |
|
99 yield host, agent |
|
100 |
|
101 |
|
102 def apply_hosts_lldp (options, hosts) : |
|
103 """ |
|
104 Query host LLDP info. |
|
105 """ |
|
106 |
|
107 # second pass to discver links |
|
108 for host, agent in hosts_lldp(options, hosts) : |
|
109 try : |
|
110 remotes = list(agent.remotes()) |
|
111 except snmp.SNMPError as ex : |
|
112 log.warn("%s: broken lldp remotes: %s", host, ex) |
|
113 continue |
|
114 |
|
115 for port, remote in remotes : |
|
116 port = agent.port(port) |
|
117 |
|
118 log.info("%s: %s: %s", host, port, remote) |
|
119 |
|
120 yield host, agent.local, port, remote |
|
121 |
|
122 def apply_hosts_vlan (options, hosts) : |
|
123 """ |
|
124 Query host VLAN ports. |
|
125 |
|
126 Yields host, { port: (untagged, [tagged]) } |
|
127 """ |
|
128 |
|
129 _hosts_vlan = list(hosts_vlan(options, hosts)) |
|
130 |
|
131 for host, agent in _hosts_vlan : |
|
132 # only one untagged vlan / port |
|
133 vlan_untagged = { } |
|
134 |
|
135 # multiple taggd vlans / port |
|
136 vlan_tagged = collections.defaultdict(set) |
|
137 |
|
138 for vlan, (tagged, untagged) in agent.vlan_ports() : |
|
139 log.info("%s: %s: %s + %s", host, vlan, tagged, untagged) |
|
140 |
|
141 for port in tagged : |
|
142 vlan_tagged[port].add(vlan) |
|
143 |
|
144 for port in untagged : |
|
145 if port in vlan_untagged : |
|
146 log.warning("%s: duplicate untagged vlan %s for port %s on vlan %s", host, vlan, port, vlan_untagged[port]) |
|
147 |
|
148 vlan_untagged[port] = vlan |
|
149 |
|
150 for port in set(vlan_untagged) | set(vlan_tagged) : |
|
151 yield host, port, vlan_untagged.get(port), tuple(vlan_tagged[port]) |
|
152 |
|
153 def apply_hosts_bridge (options, hosts) : |
|
154 """ |
|
155 Query host bridge tables. |
|
156 |
|
157 Yields host, { port: (macs) } |
|
158 """ |
|
159 |
|
160 for host, agent in hosts_bridge(options, hosts) : |
|
161 ports = collections.defaultdict(list) |
|
162 |
|
163 try : |
|
164 vlan_fdb_ports = list(agent.vlan_fdb_ports()) |
|
165 except snmp.SNMPError as ex : |
|
166 log.warn("%s: broken dot1q fdb: %s", host, ex) |
|
167 continue |
|
168 |
|
169 if vlan_fdb_ports : |
|
170 log.info("%s: have dot1q ports", host) |
|
171 |
|
172 for ether, port, vlan in agent.vlan_fdb_ports() : |
|
173 if not port : |
|
174 # XXX: unknown? |
|
175 continue |
|
176 |
|
177 ports[(port, vlan)].append(ether) |
|
178 else : |
|
179 try : |
|
180 fdb_ports = list(agent.fdb_ports()) |
|
181 except snmp.SNMPError as ex : |
|
182 log.warn("%s: broken dot1q fdb: %s", host, ex) |
|
183 continue |
|
184 |
|
185 # fallback to dot1d fdb |
|
186 log.info("%s: fallback to dot1d", host) |
|
187 |
|
188 for ether, port in agent.fdb_ports() : |
|
189 if not port : |
|
190 # XXX: unknown? |
|
191 continue |
|
192 |
|
193 ports[(port, None)].append(ether) |
|
194 |
|
195 for (port, vlan), ethers in ports.iteritems() : |
|
196 yield host, vlan, port, ethers |
|
197 |
|
198 def apply_hosts (options, hosts) : |
|
199 """ |
|
200 Gather data on given hosts... |
|
201 |
|
202 (host, key, value) |
|
203 """ |
|
204 |
|
205 if options.scan or options.scan_lldp : |
|
206 # discover node/port graph |
|
207 for host, local, port, remote in apply_hosts_lldp(options, hosts) : |
|
208 yield host, ('lldp', ), set((local['chassis'], )) |
|
209 |
|
210 yield host, ('port', port['port'], 'lldp', remote['chassis'], 'port', remote['port']), None |
|
211 |
|
212 if options.scan or options.scan_vlan : |
|
213 # discover vlan ports |
|
214 for host, port, untag, tagged in apply_hosts_vlan(options, hosts) : |
|
215 if untag : |
|
216 yield host, ('port', port, 'untagged', untag), None |
|
217 |
|
218 if tagged : |
|
219 yield host, ('port', port, 'tagged'), set(tagged) |
|
220 |
|
221 if options.scan or options.scan_bridge : |
|
222 # discover edge nodes |
|
223 for host, vlan, port, ethers in apply_hosts_bridge(options, hosts) : |
|
224 if vlan : |
|
225 yield host, ('port', port, 'bridge', 'vlan', vlan), set(ethers) |
|
226 else : |
|
227 yield host, ('port', port, 'bridge'), set(ethers) |
|
228 |
|
229 def main (argv) : |
|
230 """ |
|
231 SNMP polling. |
|
232 """ |
|
233 |
|
234 parser = optparse.OptionParser(main.__doc__) |
|
235 parser.add_option_group(pvl.args.parser(parser)) |
|
236 parser.add_option_group(pvl.hosts.optparser(parser)) |
|
237 parser.add_option_group(pvl.snmp.snmp.options(parser)) |
|
238 |
|
239 parser.add_option('--scan', action='store_true') |
|
240 parser.add_option('--scan-lldp', action='store_true') |
|
241 parser.add_option('--scan-vlan', action='store_true') |
|
242 parser.add_option('--scan-bridge', action='store_true') |
|
243 |
|
244 # input |
|
245 options, args = parser.parse_args(argv[1:]) |
|
246 pvl.args.apply(options) |
|
247 |
|
248 # gather SNMP data from hosts |
|
249 hosts = pvl.hosts.apply(options, args) |
|
250 |
|
251 data = collections.defaultdict(dict) |
|
252 |
|
253 for host, attr, values in apply_hosts(options, hosts) : |
|
254 log.info("[%s] %s%s", host, ' '.join(str(a) for a in attr), (': ' + ' '.join(str(value) for value in values)) if values else '') |
|
255 |
|
256 if values is None : |
|
257 data[host][attr] = None |
|
258 else : |
|
259 # merge |
|
260 data[host].setdefault(attr, set()).update(values) |
|
261 |
|
262 # output |
|
263 for host, attrs in sorted(data.items()) : |
|
264 print "{host}".format(host=host) |
|
265 |
|
266 for attr, value in sorted(attrs.items()) : |
|
267 print "\t{attr}".format(attr=' '.join(str(a) for a in attr)) |
|
268 |
|
269 if value : |
|
270 for v in sorted(value) : |
|
271 print "\t\t{value}".format(value=v) |
|
272 |
|
273 return 0 |
|
274 |
|
275 if __name__ == '__main__': |
|
276 pvl.args.main(main) |
|
277 |