90 ip = attrs['ip'], |
90 ip = attrs['ip'], |
91 mac = attrs['mac'], |
91 mac = attrs['mac'], |
92 gw = attrs['gw'], |
92 gw = attrs['gw'], |
93 |
93 |
94 first_seen = attrs['timestamp'], |
94 first_seen = attrs['timestamp'], |
95 |
95 count = 1, |
96 name = attrs['name'], |
96 |
97 last_seen = attrs['timestamp'], |
97 last_seen = attrs['timestamp'], |
98 last_msg = attrs['state'], |
98 state = attrs['state'], |
|
99 |
|
100 name = attrs.get('name'), |
|
101 error = attrs.get('error'), |
99 ) |
102 ) |
100 result = self.engine.execute(query, **attrs) |
103 result = self.engine.execute(query) |
101 id, = result.inserted_primary_key |
104 id, = result.inserted_primary_key |
102 |
105 |
103 return id |
106 return id |
104 |
107 |
105 def update (self, attrs) : |
108 def update (self, attrs) : |
107 UPDATE existing host, or return False if not found. |
110 UPDATE existing host, or return False if not found. |
108 """ |
111 """ |
109 |
112 |
110 table = db.dhcp_hosts |
113 table = db.dhcp_hosts |
111 |
114 |
112 query = table.update( |
115 query = table.update() |
113 # where |
116 query = query.where((table.c.ip == attrs['ip']) & (table.c.mac == attrs['mac']) & (table.c.gw == attrs['gw'])) |
114 (table.c.ip == attrs['ip']) & (table.c.mac == attrs['mac']) & (table.c.gw == attrs['gw']), |
117 query = query.values( |
|
118 count = db.func.coalesce(table.c.count, 0) + 1, |
115 |
119 |
116 # set |
120 # set |
117 name = attrs['name'], |
|
118 last_seen = attrs['timestamp'], |
121 last_seen = attrs['timestamp'], |
119 last_msg = attrs['state'], |
122 state = attrs['state'], |
120 ) |
123 ) |
121 result = self.engine.execute(query, **attrs) |
124 |
|
125 if 'name' in attrs : |
|
126 query = query.values(name = attrs['name']) |
|
127 |
|
128 if 'error' in attrs : |
|
129 query = query.values(error = attrs['error']) |
|
130 |
|
131 result = self.engine.execute(query) |
122 |
132 |
123 # any matched rows? |
133 # any matched rows? |
124 return result.rowcount > 0 |
134 return result.rowcount > 0 |
125 |
135 |
126 def process (self, item) : |
136 def process (self, item) : |
127 """ |
137 """ |
128 Process given DHCP syslog message to update the hosts table. |
138 Process given DHCP syslog message to update the hosts table. |
129 """ |
139 """ |
|
140 |
|
141 attrs = {} |
130 |
142 |
131 # ignore unless we have enough info to fully identify the client |
143 # ignore unless we have enough info to fully identify the client |
132 # this means that we omit DHCPDISCOVER messages, but we get the OFFER/REQUEST/ACK |
144 # this means that we omit DHCPDISCOVER messages, but we get the OFFER/REQUEST/ACK |
133 if any(name not in item for name in ('lease', 'hwaddr', 'gateway')) : |
145 if any(name not in item for name in ('lease', 'hwaddr', 'gateway')) : |
134 # ignore; we require these |
146 # ignore; we require these |
135 return |
147 return |
136 |
148 |
|
149 # do not override error from request on NAK; clear otherwise |
|
150 if item.get('type') == 'DHCPNAK' : |
|
151 pass |
|
152 else : |
|
153 attrs['error'] = item.get('error') |
|
154 |
|
155 # do not override name unless known |
|
156 if item.get('name') : |
|
157 attrs['name'] = item.get('name') |
|
158 |
137 # db: syslog |
159 # db: syslog |
138 ATTR_MAP = ( |
160 ATTR_MAP = ( |
139 ('ip', 'lease'), |
161 ('ip', 'lease'), |
140 ('mac', 'hwaddr'), |
162 ('mac', 'hwaddr'), |
141 ('gw', 'gateway'), |
163 ('gw', 'gateway'), |
142 ('name', 'hostname'), |
|
143 |
164 |
144 ('timestamp', 'timestamp'), |
165 ('timestamp', 'timestamp'), |
145 ('state', 'type'), |
166 ('state', 'type'), |
146 ('error', 'error'), |
|
147 ) |
167 ) |
148 |
168 |
149 # attrs |
169 # generic attrs |
150 attrs = dict((key, item.get(name)) for key, name in ATTR_MAP) |
170 for key, name in ATTR_MAP : |
|
171 attrs[key] = item.get(name) |
151 |
172 |
152 # update existing? |
173 # update existing? |
153 if self.update(attrs) : |
174 if self.update(attrs) : |
154 log.info("Update: %s", attrs) |
175 log.info("Update: %s", attrs) |
155 |
176 |
210 log.debug("tick") |
234 log.debug("tick") |
211 |
235 |
212 def main (argv) : |
236 def main (argv) : |
213 options, args = parse_options(argv) |
237 options, args = parse_options(argv) |
214 |
238 |
|
239 # db |
|
240 if not options.database : |
|
241 log.error("No database given") |
|
242 return 1 |
|
243 |
|
244 log.info("Open up database: %s", options.database) |
|
245 database = DHCPHostsDatabase(options.database) |
|
246 |
|
247 if options.create : |
|
248 database.create() |
|
249 |
215 # syslog |
250 # syslog |
216 log.info("Open up syslog...") |
251 log.info("Open up syslog...") |
217 syslog_parser = pvl.syslog.parser.SyslogParser(prog=DHCP_SYSLOG_PROG) # filter by prog |
252 syslog_parser = pvl.syslog.parser.SyslogParser(prog=DHCP_SYSLOG_PROG) # filter by prog |
218 |
253 |
219 if options.syslog_fifo : |
254 if options.syslog_fifo : |
229 # one-shot file read |
264 # one-shot file read |
230 source = pvl.syslog.tail.TailFile(options.syslog_file) |
265 source = pvl.syslog.tail.TailFile(options.syslog_file) |
231 poll = False # do not poll-loop |
266 poll = False # do not poll-loop |
232 |
267 |
233 else : |
268 else : |
234 log.error("No syslog source given") |
269 log.warning("No syslog source given") |
235 return 1 |
270 return 0 |
236 |
271 |
237 # db |
|
238 log.info("Open up database: %s", options.database) |
|
239 database = DHCPHostsDatabase(options.database) |
|
240 |
|
241 if options.create : |
|
242 database.create() |
|
243 |
|
244 # handler + main |
272 # handler + main |
245 handler = DHCPSyslogHandler(database) |
273 handler = DHCPSyslogHandler(database) |
246 |
274 |
247 log.info("Enter mainloop...") |
275 log.info("Enter mainloop...") |
248 handler.main(source, poll) |
276 handler.main(source, poll) |