Mark Bergsma has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/393097 )

Change subject: [WiP] Support per-service-IP BGP MED values
......................................................................

[WiP] Support per-service-IP BGP MED values

Add a per-service 'bgp-med' configuation option that allows overriding
the global MED value per service.

Note that as multiple services may share a service IP (e.g. port 80 and
443), the MED values are required to be equal.

These changes also add scaffolding for later support for announcing and
retracting service IPs based on the service health.

Bug: T165764
Change-Id: I08687e8072dd8f2ae88833a1660d154ae28c7add
---
M pybal/bgpfailover.py
M pybal/coordinator.py
M pybal/ipvs.py
3 files changed, 59 insertions(+), 27 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/operations/debs/pybal 
refs/changes/97/393097/1

diff --git a/pybal/bgpfailover.py b/pybal/bgpfailover.py
index 1b6865f..70811ed 100755
--- a/pybal/bgpfailover.py
+++ b/pybal/bgpfailover.py
@@ -22,6 +22,7 @@
 
     prefixes = {}
     peerings = {}
+    ipServices = {}
 
     def __init__(self, globalConfig):
         if not globalConfig.getboolean('bgp', False):
@@ -35,29 +36,22 @@
             myASN = self.globalConfig.getint('bgp-local-asn')
             asPath = self.globalConfig.get('bgp-as-path', str(myASN))
             asPath = [int(asn) for asn in asPath.split()]
-            med = self.globalConfig.getint('bgp-med', 0)
-            baseAttrs = [bgp.OriginAttribute(), bgp.ASPathAttribute(asPath)]
-            if med: baseAttrs.append(bgp.MEDAttribute(med))
 
-            attributes = {}
+            defaultMED = self.globalConfig.getint('bgp-med', 0)
+
             try:
-                attributes[(bgp.AFI_INET, bgp.SAFI_UNICAST)] = 
bgp.FrozenAttributeDict(baseAttrs + [
-                    
bgp.NextHopAttribute(self.globalConfig['bgp-nexthop-ipv4'])])
+                nexthopIPv4 = self.globalConfig['bgp-nexthop-ipv4']
             except KeyError:
                 if (bgp.AFI_INET, bgp.SAFI_UNICAST) in BGPFailover.prefixes:
                     raise ValueError("IPv4 BGP NextHop (global configuration 
variable 'bgp-nexthop-ipv4') not set")
 
             try:
-                attributes[(bgp.AFI_INET6, bgp.SAFI_UNICAST)] = 
bgp.FrozenAttributeDict(baseAttrs + [
-                    bgp.MPReachNLRIAttribute((bgp.AFI_INET6, bgp.SAFI_UNICAST,
-                                             
bgp.IPv6IP(self.globalConfig['bgp-nexthop-ipv6']), []))])
+                nexthopIPv6 = self.globalConfig['bgp-nexthop-ipv6']
             except KeyError:
                 if (bgp.AFI_INET6, bgp.SAFI_UNICAST) in BGPFailover.prefixes:
                     raise ValueError("IPv6 BGP NextHop (global configuration 
variable 'bgp-nexthop-ipv6') not set")
 
-            advertisements = set([bgp.Advertisement(prefix, attributes[af], af)
-                                  for af in attributes.keys()
-                                  for prefix in BGPFailover.prefixes.get(af, 
set())])
+            advertisements = self.buildAdvertisements(myASN, asPath, 
nexthopIPv4, nexthopIPv6, defaultMED)
 
             bgpPeerAddress = self.globalConfig.get('bgp-peer-address', 
'').strip()
             if bgpPeerAddress[0] != '[': bgpPeerAddress = "[ \"{}\" 
]".format(bgpPeerAddress)
@@ -66,7 +60,7 @@
 
             for peerAddr in peerAddresses:
                 peering = bgp.NaiveBGPPeering(myASN, peerAddr)
-                peering.setEnabledAddressFamilies(set(attributes.keys()))
+                peering.setEnabledAddressFamilies(set(self.prefixes.keys()))
                 peering.setAdvertisements(advertisements)
 
                 log.info("Starting BGP session with peer {}".format(peerAddr))
@@ -102,13 +96,49 @@
         peering.setAdvertisements(set())
         return peering.manualStop()
 
+    def buildAdvertisements(self, myASN, asPath, nexthopIPv4, nexthopIPv6, 
defaultMED):
+        baseAttrs = [bgp.OriginAttribute(), bgp.ASPathAttribute(asPath)]
+
+        advertisements = set()
+        for af in self.prefixes:
+            for prefix in self.prefixes[af]:
+                if af[0] == (bgp.AFI_INET):
+                    attrList = baseAttrs + [bgp.NextHopAttribute(nexthopIPv4)]
+                elif af[0] == (bgp.AFI_INET6):
+                    attrList = (baseAttrs
+                        + [bgp.MPReachNLRIAttribute((af, 
bgp.IPv6IP(nexthopIPv6), []))])
+                else:
+                    raise ValueError("Unsupported address family 
{}".format(af))
+
+                # This service IP may use a non-default MED
+                med = self.ipServices[prefix][0]['med'] # Guaranteed to exist, 
may be None
+                attrList.append(bgp.MEDAttribute(med or defaultMED))
+
+                attributes = bgp.FrozenAttributeDict(attrList)
+                advertisements.add(bgp.Advertisement(prefix, attributes, af))
+
+        return advertisements
+
     @classmethod
-    def addPrefix(cls, prefix):
-        try:
-            if ':' not in prefix:
-                cls.prefixes.setdefault((bgp.AFI_INET, bgp.SAFI_UNICAST), 
set()).add(bgp.IPv4IP(prefix))
-            else:
-                cls.prefixes.setdefault((bgp.AFI_INET6, bgp.SAFI_UNICAST), 
set()).add(bgp.IPv6IP(prefix))
-        except NameError:
-            # bgp not imported
-            pass
+    def associateService(cls, ip, lvsservice, med):
+        if ':' not in ip:
+            af = (bgp.AFI_INET, bgp.SAFI_UNICAST)
+            prefix = bgp.IPv4IP(ip)
+        else:
+            af = (bgp.AFI_INET6, bgp.SAFI_UNICAST)
+            prefix = bgp.IPv6IP(ip)
+
+        # All services need to agree on the same MED for this IP
+        if prefix in cls.ipServices and not med == 
cls.ipServices[prefix][0]['med']:
+            raise ValueError(
+                "LVS service {} MED value {} differs from other MED values for 
IP {}".format(
+                lvsservice.name, med, ip))
+
+        service_state = {
+            'lvsservice': lvsservice,
+            'af': af,
+            'med': med
+        }
+
+        cls.ipServices.setdefault(prefix, []).append(service_state)
+        cls.prefixes.setdefault(af, set()).add(prefix)
diff --git a/pybal/coordinator.py b/pybal/coordinator.py
index a526b5b..1bf108c 100755
--- a/pybal/coordinator.py
+++ b/pybal/coordinator.py
@@ -15,6 +15,7 @@
 from twisted.python import failure
 
 from pybal import config, util
+from pybal.bgpfailover import BGPFailover
 from pybal.metrics import Counter, Gauge
 
 log = util.log
@@ -337,6 +338,12 @@
         self.configObserver = config.ConfigurationObserver.fromUrl(self, 
configUrl)
         self.configObserver.startObserving()
 
+        if self.lvsservice.configuration.getboolean('bgp', True):
+            # Pass a per-service(-ip) MED if one is provided
+            med = self.lvsservice.configuration.get('bgp-med', None)
+            # Associcate service ip to this coordinator for BGP announcements
+            BGPFailover.associateService(self.lvsservice.ip, self.lvsservice, 
med)
+
         self.metrics['depool_threshold'].labels(
             **self.metric_labels
             ).set(self.lvsservice.getDepoolThreshold())
diff --git a/pybal/ipvs.py b/pybal/ipvs.py
index 8365e50..7538b9f 100644
--- a/pybal/ipvs.py
+++ b/pybal/ipvs.py
@@ -5,7 +5,6 @@
 LVS state/configuration classes for PyBal
 """
 from . import util
-from pybal.bgpfailover import BGPFailover
 
 import os
 log = util.log
@@ -191,10 +190,6 @@
 
         self.ipvsManager.DryRun = configuration.getboolean('dryrun', False)
         self.ipvsManager.Debug = configuration.getboolean('debug', False)
-
-        if self.configuration.getboolean('bgp', True):
-            # Add service ip to the BGP announcements
-            BGPFailover.addPrefix(self.ip)
 
         self.createService()
 

-- 
To view, visit https://gerrit.wikimedia.org/r/393097
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I08687e8072dd8f2ae88833a1660d154ae28c7add
Gerrit-PatchSet: 1
Gerrit-Project: operations/debs/pybal
Gerrit-Branch: master
Gerrit-Owner: Mark Bergsma <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to