Index: trunk/pybal/pybal/ipvs.py |
— | — | @@ -88,7 +88,7 @@ |
89 | 89 | server: Server |
90 | 90 | """ |
91 | 91 | |
92 | | - return '-d ' + cls.subCommandService(service) + ' -r ' + server.host |
| 92 | + return '-d ' + cls.subCommandService(service) + ' -r ' + (server.ip or server.host) |
93 | 93 | commandRemoveServer = classmethod(commandRemoveServer) |
94 | 94 | |
95 | 95 | def commandAddServer(cls, service, server): |
— | — | @@ -100,7 +100,7 @@ |
101 | 101 | server: Server |
102 | 102 | """ |
103 | 103 | |
104 | | - cmd = '-a ' + cls.subCommandService(service) + ' -r ' + server.host |
| 104 | + cmd = '-a ' + cls.subCommandService(service) + ' -r ' + (server.ip or server.host) |
105 | 105 | |
106 | 106 | # Include weight if specified |
107 | 107 | if server.weight: |
— | — | @@ -118,7 +118,7 @@ |
119 | 119 | server: Server |
120 | 120 | """ |
121 | 121 | |
122 | | - cmd = '-e ' + cls.subCommandService(service) + ' -r ' + server.host |
| 122 | + cmd = '-e ' + cls.subCommandService(service) + ' -r ' + (server.ip or server.host) |
123 | 123 | |
124 | 124 | # Include weight if specified |
125 | 125 | if server.weight: |
— | — | @@ -141,7 +141,7 @@ |
142 | 142 | """Constructor""" |
143 | 143 | |
144 | 144 | self.name = name |
145 | | - self.servers = {} |
| 145 | + self.servers = set() |
146 | 146 | |
147 | 147 | if (protocol not in self.SVC_PROTOS |
148 | 148 | or scheduler not in self.SVC_SCHEDULERS): |
— | — | @@ -179,11 +179,6 @@ |
180 | 180 | # Remove a previous service and add the new one |
181 | 181 | cmdList = [self.ipvsManager.commandRemoveService(self.service()), |
182 | 182 | self.ipvsManager.commandAddService(self.service())] |
183 | | - |
184 | | - # Add realservers |
185 | | - for server in self.servers.itervalues(): |
186 | | - cmdList.append(self.ipvsManager.commandAddServer(self.service(), server)) |
187 | | - |
188 | 183 | self.ipvsManager.modifyState(cmdList) |
189 | 184 | |
190 | 185 | def assignServers(self, newServers): |
— | — | @@ -191,49 +186,24 @@ |
192 | 187 | Takes a (new) set of servers (as a host->Server dictionary) and updates |
193 | 188 | the LVS state accordingly. |
194 | 189 | """ |
195 | | - |
196 | | - # Compute set of servers to delete and edit |
197 | | - removeServers, editServers = [], [] |
198 | | - for hostname, server in self.servers.iteritems(): |
199 | | - if hostname not in newServers: |
200 | | - removeServers.append(server) |
201 | | - else: |
202 | | - editServers.append(server) |
203 | | - |
204 | | - # Compute set of servers to add |
205 | | - addServers = [] |
206 | | - for hostname, server in newServers.iteritems(): |
207 | | - if hostname not in self.servers: |
208 | | - addServers.append(server) |
209 | | - |
210 | | - self.servers = dict(newServers) # shallow copy |
211 | | - cmdList = [] |
212 | | - |
213 | | - # Add new servers first |
214 | | - for server in addServers: |
215 | | - cmdList.append(self.ipvsManager.commandAddServer(self.service(), server)) |
216 | | - server.pooled = True |
217 | | - |
218 | | - # Edit existing servers |
219 | | - for server in editServers: |
220 | | - cmdList.append(self.ipvsManager.commandEditServer(self.service(), server)) |
221 | 190 | |
222 | | - # Remove servers |
223 | | - for server in removeServers: |
224 | | - cmdList.append(self.ipvsManager.commandRemoveServer(self.service(), server)) |
225 | | - server.pooled = False |
| 191 | + cmdList = [self.ipvsManager.commandAddServer(self.service(), server) for server in newServers - self.servers] + \ |
| 192 | + [self.ipvsManager.commandEditServer(self.service(), server) for server in newServers & self.servers] + \ |
| 193 | + [self.ipvsManager.commandRemoveServer(self.service(), server) for server in self.servers - newServers] |
226 | 194 | |
| 195 | + self.servers = newServers |
227 | 196 | self.ipvsManager.modifyState(cmdList) |
228 | 197 | |
229 | 198 | def addServer(self, server): |
230 | 199 | """Adds (pools) a single Server to the LVS state""" |
231 | 200 | |
232 | | - if server.host not in self.servers: |
| 201 | + if server not in self.servers: |
233 | 202 | cmdList = [self.ipvsManager.commandAddServer(self.service(), server)] |
234 | 203 | else: |
| 204 | + print "WARNING: possible bug; adding previously existing server to LVS" |
235 | 205 | cmdList = [self.ipvsManager.commandEditServer(self.service(), server)] |
236 | 206 | |
237 | | - self.servers[server.host] = server |
| 207 | + self.servers.add(server) |
238 | 208 | |
239 | 209 | self.ipvsManager.modifyState(cmdList) |
240 | 210 | server.pooled = True |
— | — | @@ -243,7 +213,12 @@ |
244 | 214 | |
245 | 215 | cmdList = [self.ipvsManager.commandRemoveServer(self.service(), server)] |
246 | 216 | |
247 | | - del self.servers[server.host] # May raise KeyError |
| 217 | + self.servers.remove(server) # May raise KeyError |
248 | 218 | |
249 | 219 | server.pooled = False |
250 | | - self.ipvsManager.modifyState(cmdList) |
\ No newline at end of file |
| 220 | + self.ipvsManager.modifyState(cmdList) |
| 221 | + |
| 222 | + def getDepoolThreshold(self): |
| 223 | + """Returns the threshold below which no more down servers will be depooled""" |
| 224 | + |
| 225 | + return self.configuration.getfloat('depool-threshold', .5) |
\ No newline at end of file |
Index: trunk/pybal/pybal/monitors/proxyfetch.py |
— | — | @@ -84,7 +84,7 @@ |
85 | 85 | def _fetchSuccessful(self, result): |
86 | 86 | """Called when getProxyPage is finished successfully.""" |
87 | 87 | |
88 | | - print self.server.host + ': Fetch successful, %.2f s' % (seconds() - self.checkStartTime) |
| 88 | + self.report('Fetch successful, %.3f s' % (seconds() - self.checkStartTime)) |
89 | 89 | self._resultUp() |
90 | 90 | |
91 | 91 | return result |
— | — | @@ -92,7 +92,7 @@ |
93 | 93 | def _fetchFailed(self, failure): |
94 | 94 | """Called when getProxyPage finished with a failure.""" |
95 | 95 | |
96 | | - print self.server.host + ': Fetch failed, %.2f s' % (seconds() - self.checkStartTime) |
| 96 | + self.report('Fetch failed, %.3f s' % (seconds() - self.checkStartTime)) |
97 | 97 | |
98 | 98 | self._resultDown(failure.getErrorMessage()) |
99 | 99 | |
Index: trunk/pybal/pybal/monitor.py |
— | — | @@ -65,6 +65,13 @@ |
66 | 66 | if self.coordinator: |
67 | 67 | self.coordinator.resultDown(self, reason) |
68 | 68 | |
| 69 | + def report(self, text): |
| 70 | + """ |
| 71 | + Common method for reporting/logging check results |
| 72 | + """ |
| 73 | + |
| 74 | + print "[%s] %s (%s): %s" % (self.__name__, self.server.host, self.server.textStatus(), text) |
| 75 | + |
69 | 76 | def _getConfigStringList(self, optionname): |
70 | 77 | """ |
71 | 78 | Takes a (string) value, eval()s it and checks whether it |
Index: trunk/pybal/pybal/pybal.py |
— | — | @@ -34,15 +34,30 @@ |
35 | 35 | """Constructor""" |
36 | 36 | |
37 | 37 | self.host = host |
| 38 | + self.ip = None |
38 | 39 | self.port = 80 |
39 | 40 | |
40 | 41 | self.monitors = [] |
41 | 42 | |
| 43 | + # A few invariants that SHOULD be maintained (but currently may not be): |
| 44 | + # P0: pooled => enabled |
| 45 | + # P1: up => pooled \/ !enabled |
| 46 | + # P2: pooled => up \/ !canDepool |
| 47 | + |
42 | 48 | self.weight = self.DEF_WEIGHT |
43 | 49 | self.up = self.DEF_STATE |
44 | 50 | self.pooled = self.up |
45 | | - self.enabled = self.up |
| 51 | + self.enabled = True |
| 52 | + self.modified = None |
46 | 53 | |
| 54 | + #self.resolveHostname() |
| 55 | + |
| 56 | + def __eq__(self, other): |
| 57 | + return instanceof(other, Server) and self.host == other.host |
| 58 | + |
| 59 | + def __hash__(self): |
| 60 | + return hash(self.host) |
| 61 | + |
47 | 62 | def addMonitor(self, monitor): |
48 | 63 | """Adds a monitor instance to the list""" |
49 | 64 | |
— | — | @@ -60,29 +75,103 @@ |
61 | 76 | |
62 | 77 | for monitor in self.monitors: |
63 | 78 | self.removeMonitor(monitor) |
| 79 | + |
| 80 | + def resolveHostname(self): |
| 81 | + """Attempts to resolve the server's hostname to an IP address for better reliability.""" |
| 82 | + |
| 83 | + if not self.ip: |
| 84 | + from twisted.names import client |
| 85 | + return client.lookupAddress(self.host).addCallback(self._hostnameResolved) |
| 86 | + else: |
| 87 | + from twisted.internet import defer |
| 88 | + return defer.succeed(self.ip) |
64 | 89 | |
65 | | - def merge(self, server): |
66 | | - """Merges in configuration attributes of another instance""" |
| 90 | + def _hostnameResolved(self, lookupResult): |
| 91 | + try: |
| 92 | + self.ip = lookupResult[0][0].payload.dottedQuad() # FIXME: IPv6 |
| 93 | + except: |
| 94 | + pass |
| 95 | + |
| 96 | + def destroy(self): |
| 97 | + self.enabled = False |
| 98 | + self.removeMonitors() |
| 99 | + |
| 100 | + def createMonitoringInstances(self, coordinator, lvsservice): |
| 101 | + """Creates and runs monitoring instances for this Server""" |
67 | 102 | |
68 | | - for key, value in server.__dict__.iteritems(): |
69 | | - if (key, type(value)) in self.allowedConfigKeys: |
70 | | - self.__dict__[key] = value |
| 103 | + try: |
| 104 | + monitorlist = eval(lvsservice.configuration['monitors']) |
| 105 | + except KeyError: |
| 106 | + print "LVS service", lvsservice.name, "does not have a 'monitors' configuration option set." |
| 107 | + raise |
| 108 | + |
| 109 | + if type(monitorlist) != list: |
| 110 | + print "option 'monitors' in LVS service section", lvsservice.name, \ |
| 111 | + "is not a Python list." |
| 112 | + else: |
| 113 | + for monitorname in monitorlist: |
| 114 | + try: |
| 115 | + # FIXME: this is a hack? |
| 116 | + monitormodule = getattr(sys.modules['pybal.monitors'], monitorname.lower()) |
| 117 | + monitorclass = getattr(monitormodule , monitorname + 'MonitoringProtocol' ) |
| 118 | + except AttributeError: |
| 119 | + print "Monitor", monitorname, "does not exist." |
| 120 | + else: |
| 121 | + monitor = monitorclass(coordinator, self, lvsservice.configuration) |
| 122 | + self.addMonitor(monitor) |
| 123 | + monitor.run() |
| 124 | + |
| 125 | + def calcStatus(self): |
| 126 | + """AND quantification of monitor.up over all monitoring instances of a single Server""" |
| 127 | + |
| 128 | + # Global status is up iff all monitors report up |
| 129 | + return reduce(lambda b,monitor: b and monitor.up, self.monitors, self.monitors != []) |
71 | 130 | |
| 131 | + def calcPartialStatus(self): |
| 132 | + """OR quantification of monitor.up over all monitoring instances of a single Server""" |
| 133 | + |
| 134 | + # Partial status is up iff one of the monitors reports up |
| 135 | + return reduce(lambda b,monitor: b or monitor.up, self.monitors, self.monitors == []) |
| 136 | + |
| 137 | + def textStatus(self): |
| 138 | + return "%s/%s/%s" % (self.enabled and "enabled" or "disabled", |
| 139 | + self.up and "up" or (self.calcPartialStatus() and "partially up" or "down"), |
| 140 | + self.pooled and "pooled" or "not pooled") |
| 141 | + |
| 142 | + def maintainState(self): |
| 143 | + """Maintains a few invariants on configuration changes""" |
| 144 | + |
| 145 | + # P0 |
| 146 | + if not self.enabled: |
| 147 | + self.pooled = False |
| 148 | + # P1 |
| 149 | + if not self.pooled and self.enabled: |
| 150 | + self.up = False |
| 151 | + |
| 152 | + def merge(self, configuration): |
| 153 | + """Merges in configuration from a dictionary of (allowed) attributes""" |
| 154 | + |
| 155 | + for key, value in configuration.iteritems(): |
| 156 | + if (key, type(value)) not in self.allowedConfigKeys: |
| 157 | + del configuration[key] |
| 158 | + |
| 159 | + # Overwrite configuration |
| 160 | + self.__dict__.update(configuration) |
| 161 | + self.maintainState() |
| 162 | + self.modified = True # Indicate that this instance previously existed |
| 163 | + |
| 164 | + @classmethod |
72 | 165 | def buildServer(cls, configuration): |
73 | 166 | """ |
74 | 167 | Factory method which builds a Server instance from a |
75 | 168 | dictionary of (allowed) configuration attributes |
76 | 169 | """ |
77 | 170 | |
78 | | - for key, value in configuration.iteritems(): |
79 | | - if (key, type(value)) not in cls.allowedConfigKeys: |
80 | | - del configuration[key] |
81 | | - |
82 | 171 | server = cls(configuration['host']) # create a new instance... |
83 | | - server.__dict__.update(configuration) # ...and override attributes |
| 172 | + server.merge(configuration) # ...and override attributes |
| 173 | + server.modified = False |
84 | 174 | |
85 | 175 | return server |
86 | | - buildServer = classmethod(buildServer) |
87 | 176 | |
88 | 177 | class Coordinator: |
89 | 178 | """ |
— | — | @@ -99,7 +188,7 @@ |
100 | 189 | |
101 | 190 | self.lvsservice = lvsservice |
102 | 191 | self.servers = {} |
103 | | - self.pooledDownServers = [] |
| 192 | + self.pooledDownServers = set() |
104 | 193 | self.configHash = None |
105 | 194 | self.serverConfigURL = configURL |
106 | 195 | |
— | — | @@ -107,53 +196,16 @@ |
108 | 197 | from twisted.internet import task |
109 | 198 | task.LoopingCall(self.loadServers).start(self.intvLoadServers) |
110 | 199 | |
111 | | - def assignServers(self, servers): |
| 200 | + def assignServers(self): |
112 | 201 | """ |
113 | 202 | Takes a new set of servers (as a host->Server dict) and |
114 | 203 | hands them over to LVSService |
115 | 204 | """ |
116 | | - |
117 | | - self.servers = servers |
118 | 205 | |
119 | 206 | # Hand over enabled servers to LVSService |
120 | 207 | self.lvsservice.assignServers( |
121 | | - dict([(server.host, server) for server in servers.itervalues() if server.enabled])) |
122 | | - |
123 | | - def createMonitoringInstances(self, servers=None): |
124 | | - """Creates and runs monitoring instances for a list of Servers""" |
125 | | - |
126 | | - # Use self.servers by default |
127 | | - if servers is None: |
128 | | - servers = self.servers.itervalues() |
129 | | - |
130 | | - for server in servers: |
131 | | - if not server.enabled: continue |
132 | | - |
133 | | - try: |
134 | | - monitorlist = eval(self.lvsservice.configuration['monitors']) |
135 | | - except KeyError: |
136 | | - print "LVS service", self.lvsservice.name, "does not have a 'monitors' configuration option set." |
| 208 | + set([server for server in self.servers.itervalues() if server.pooled])) |
137 | 209 | |
138 | | - if type(monitorlist) != list: |
139 | | - print "option 'monitors' in LVS service section", self.lvsservice.name, \ |
140 | | - "is not a Python list." |
141 | | - else: |
142 | | - for monitorname in monitorlist: |
143 | | - try: |
144 | | - # FIXME: this is a hack? |
145 | | - monitormodule = getattr(sys.modules['pybal.monitors'], monitorname.lower()) |
146 | | - monitorclass = getattr(monitormodule , monitorname + 'MonitoringProtocol' ) |
147 | | - server.addMonitor(monitorclass(self, server, self.lvsservice.configuration)) |
148 | | - except AttributeError: |
149 | | - print "Monitor", monitorname, "does not exist." |
150 | | - |
151 | | - # Set initial status |
152 | | - #server.up = self.calcStatus(server) |
153 | | - |
154 | | - # Run all instances |
155 | | - for monitor in server.monitors: |
156 | | - monitor.run() |
157 | | - |
158 | 210 | def resultDown(self, monitor, reason=None): |
159 | 211 | """ |
160 | 212 | Accepts a 'down' notification status result from a single monitoring instance |
— | — | @@ -162,11 +214,11 @@ |
163 | 215 | |
164 | 216 | server = monitor.server |
165 | 217 | |
166 | | - print 'Monitoring instance', monitor.name(), 'reports server', server.host, 'down:', (reason or '(reason unknown)') |
| 218 | + print "Monitoring instance %s reports servers %s (%s) down:" % (monitor.name(), server.host, server.textStatus()), (reason or '(reason unknown)') |
167 | 219 | |
168 | 220 | if server.up: |
169 | 221 | server.up = False |
170 | | - self.depool(server) |
| 222 | + if server.pooled: self.depool(server) |
171 | 223 | |
172 | 224 | def resultUp(self, monitor): |
173 | 225 | """ |
— | — | @@ -176,30 +228,21 @@ |
177 | 229 | |
178 | 230 | server = monitor.server |
179 | 231 | |
180 | | - if not server.up and self.calcStatus(server): |
| 232 | + if not server.up and server.calcStatus(): |
| 233 | + print "Server %s (%s) is up" % (server.host, server.textStatus()) |
181 | 234 | server.up = True |
182 | | - self.repool(server) |
183 | | - |
184 | | - print 'Server', server.host, 'is up' |
185 | | - |
186 | | - def calcStatus(self, server): |
187 | | - """AND quantification of monitor.up over all monitoring instances of a single Server""" |
188 | | - |
189 | | - # Global status is up iff all monitors report up |
190 | | - return reduce(lambda b,monitor: b and monitor.up, server.monitors, server.monitors != []) |
| 235 | + if server.enabled: self.repool(server) |
191 | 236 | |
192 | 237 | def depool(self, server): |
193 | 238 | """Depools a single Server, if possible""" |
194 | 239 | |
195 | | - if not server.pooled: return |
| 240 | + assert server.pooled |
196 | 241 | |
197 | | - if self.canDepool(server): |
| 242 | + if self.canDepool(): |
198 | 243 | self.lvsservice.removeServer(server) |
199 | | - try: self.pooledDownServers.remove(server) |
200 | | - except ValueError: pass |
| 244 | + self.pooledDownServers.discard(server) |
201 | 245 | else: |
202 | | - if server not in self.pooledDownServers: |
203 | | - self.pooledDownServers.append(server) |
| 246 | + self.pooledDownServers.add(server) |
204 | 247 | print 'Could not depool server', server.host, 'because of too many down!' |
205 | 248 | |
206 | 249 | def repool(self, server): |
— | — | @@ -208,28 +251,28 @@ |
209 | 252 | not be depooled then because of too many hosts down. |
210 | 253 | """ |
211 | 254 | |
212 | | - if not server.pooled and server.enabled: |
| 255 | + assert server.enabled |
| 256 | + |
| 257 | + if not server.pooled: |
213 | 258 | self.lvsservice.addServer(server) |
| 259 | + else: |
| 260 | + print "Leaving previously pooled but down server", server.host, "pooled" |
214 | 261 | |
215 | 262 | # If it had been pooled in down state before, remove it from the list |
216 | | - try: self.pooledDownServers.remove(server) |
217 | | - except ValueError: pass |
| 263 | + self.pooledDownServers.discard(server) |
218 | 264 | |
219 | 265 | # See if we can depool any servers that could not be depooled before |
220 | | - for server in self.pooledDownServers: |
221 | | - if self.canDepool(server): |
222 | | - self.depool(server) |
223 | | - else: # then we can't depool any further servers either... |
224 | | - break |
| 266 | + while len(self.pooledDownServers) > 0 and self.canDepool(): |
| 267 | + self.depool(self.pooledDownServers.pop()) |
225 | 268 | |
226 | | - def canDepool(self, server): |
| 269 | + def canDepool(self): |
227 | 270 | """Returns a boolean denoting whether another server can be depooled""" |
228 | 271 | |
229 | 272 | # Construct a list of servers that have status 'down' |
230 | 273 | downServers = [server for server in self.servers.itervalues() if not server.up] |
231 | 274 | |
232 | | - # Only allow depooling if less than half of the total amount of servers are down |
233 | | - return len(downServers) <= len(self.servers) / 2 |
| 275 | + # The total amount of pooled servers may never drop below a configured threshold |
| 276 | + return len(self.servers) - len(downServers) >= len(self.servers) * self.lvsservice.getDepoolThreshold() |
234 | 277 | |
235 | 278 | def loadServers(self, configURL=None): |
236 | 279 | """Periodic task to load a new server list/configuration file from a specified URL.""" |
— | — | @@ -267,7 +310,6 @@ |
268 | 311 | """Parses the server list and changes the state accordingly.""" |
269 | 312 | |
270 | 313 | delServers = self.servers.copy() # Shallow copy |
271 | | - setupMonitoring = [] |
272 | 314 | |
273 | 315 | for line in lines: |
274 | 316 | line = line.rstrip('\n').strip() |
— | — | @@ -279,37 +321,23 @@ |
280 | 322 | if host in self.servers: |
281 | 323 | # Existing server. merge |
282 | 324 | server = delServers.pop(host) |
283 | | - newServer = Server.buildServer(serverdict) |
284 | | - |
285 | | - print "Merging server %s, weight %d" % ( host, newServer.weight ) |
286 | | - |
287 | | - # FIXME: Doesn't "enabled" mean "monitored, but not pooled"? |
288 | | - if not newServer.enabled and server.enabled: |
289 | | - server.removeMonitors() |
290 | | - elif newServer.enabled and not server.enabled: |
291 | | - setupMonitoring.append(newServer) |
292 | | - |
293 | | - # FIXME: BUG. .pooled and .up will remain in default state (up), |
294 | | - # and monitoring instances may not be setup. |
295 | | - server.merge(newServer) |
| 325 | + server.merge(serverdict) |
| 326 | + print "Merged %s server %s, weight %d" % (server.enabled and "enabled" or "disabled", host, server.weight) |
296 | 327 | else: |
297 | 328 | # New server |
298 | 329 | server = Server.buildServer(serverdict) |
299 | 330 | self.servers[host] = server |
300 | | - |
301 | | - print "New server %s, weight %d" % ( host, server.weight ) |
302 | | - |
303 | | - setupMonitoring.append(server) |
304 | | - |
305 | | - self.createMonitoringInstances(setupMonitoring) |
306 | | - |
| 331 | + server.createMonitoringInstances(self, self.lvsservice) |
| 332 | + print "New %s server %s, weight %d" % (server.enabled and "enabled" or "disabled", host, server.weight ) |
| 333 | + |
307 | 334 | # Remove old servers |
308 | 335 | for host, server in delServers.iteritems(): |
309 | | - server.enabled = False |
310 | | - server.removeMonitors() |
| 336 | + print "Removing server %s (no longer found in new configuration)" % host |
| 337 | + server.destroy() |
311 | 338 | del self.servers[host] |
312 | 339 | |
313 | | - self.assignServers(self.servers) # FIXME |
| 340 | + # Assign the updated list of enabled servers to the LVSService instance |
| 341 | + self.assignServers() |
314 | 342 | |
315 | 343 | class BGPFailover: |
316 | 344 | """Class for maintaining a BGP session to a router for IP address failover""" |
— | — | @@ -334,7 +362,7 @@ |
335 | 363 | for prefix in BGPFailover.prefixes: |
336 | 364 | attrSet = bgp.FrozenAttributeSet([bgp.OriginAttribute(), |
337 | 365 | bgp.ASPathAttribute(asPath), |
338 | | - bgp.NextHopAttribute(str(prefix))]) |
| 366 | + bgp.NextHopAttribute(bgp.NextHopAttribute.ANY)]) |
339 | 367 | advertisements.add(bgp.Advertisement(prefix, attrSet)) |
340 | 368 | |
341 | 369 | self.bgpPeering.setAdvertisements(advertisements) |
— | — | @@ -388,6 +416,16 @@ |
389 | 417 | return False |
390 | 418 | else: |
391 | 419 | raise ValueError |
| 420 | + |
| 421 | + def getfloat(self, key, default=None): |
| 422 | + try: |
| 423 | + return float(self[key]) |
| 424 | + except KeyError: |
| 425 | + if default is not None: |
| 426 | + return default |
| 427 | + else: |
| 428 | + raise |
| 429 | + # do not intercept ValueError |
392 | 430 | |
393 | 431 | def parseCommandLine(configuration): |
394 | 432 | """ |