You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by el...@apache.org on 2018/06/14 17:31:28 UTC

[trafficcontrol] 01/05: Implement geo-sorting of CLIENT_STEERING targets

This is an automated email from the ASF dual-hosted git repository.

elsloo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit a625fda763897b5dbf4b56b44845790ca5b8ffe2
Author: Rawlin Peters <ra...@comcast.com>
AuthorDate: Tue Feb 20 18:01:22 2018 -0700

    Implement geo-sorting of CLIENT_STEERING targets
    
    For GEO targets in a CLIENT_STEERING DS, sort the resulting list from
    most optimal to least optimal, where "optimal" means shortest distance
    from client -> edge -> origin.
    
    If there is a mix of STEERING_WEIGHT, STEERING_ORDER, and GEO targets,
    the ordering would be:
    1. negative STEERING_ORDER
    2. GEO
    3. STEERING_WEIGHT
    4. positive STEERING_ORDER
---
 .../app/lib/API/DeliveryService/Steering.pm        |   7 +-
 .../app/lib/API/DeliveryService/SteeringTarget.pm  |   1 +
 .../traffic_router/core/cache/Cache.java           |  13 ++-
 .../traffic_router/core/config/ConfigHandler.java  |   2 +-
 .../core/ds/SteeringGeolocationComparator.java     |  75 +++++++++++++
 .../traffic_router/core/ds/SteeringRegistry.java   |  10 +-
 .../traffic_router/core/ds/SteeringResult.java     |  54 +++++++++
 .../traffic_router/core/ds/SteeringTarget.java     |  54 +++++++++
 .../traffic_router/core/router/TrafficRouter.java  | 121 +++++++++++++++------
 9 files changed, 297 insertions(+), 40 deletions(-)

diff --git a/traffic_ops/app/lib/API/DeliveryService/Steering.pm b/traffic_ops/app/lib/API/DeliveryService/Steering.pm
index e036b5d..6ce009a 100644
--- a/traffic_ops/app/lib/API/DeliveryService/Steering.pm
+++ b/traffic_ops/app/lib/API/DeliveryService/Steering.pm
@@ -46,7 +46,7 @@ sub find_steering {
     my $rs_data = $self->db->resultset('SteeringView')->search({}, {order_by => ['steering_xml_id', 'target_xml_id']});
 
     while ( my $row = $rs_data->next ) {
-        if ($steering_xml_id && $row->steering_xml_id ne $steering_xml_id) {
+        if ($steering_xml_id && $row->steering_xml_id ne $steering_xml_id) { # TODO: can this be optimized into the SQL query?
             next;
         }
 
@@ -92,6 +92,9 @@ sub find_steering {
 
         my $targets = $steering_entry->{"targets"};
 
+        # TODO: add new STEERING_GEO_WEIGHT and STEERING_GEO_ORDER types, handle them here
+        # verify if STEERING_ORDER and STEERING_WEIGHT should omit latitude, longitude, and geoOrder
+        # if GEO, get the target DS's lat/long, add it to result (add it to SteeringView?)
         if ( $row->type eq "STEERING_ORDER" ) {
             push(@{$targets},{
             'deliveryService' => $row->target_xml_id,
@@ -191,6 +194,7 @@ sub update() {
     my $req_targets = $self->req->json->{'targets'};
 
     foreach my $req_target (@{$req_targets}) {
+        # TODO: update this validation to handle new GEO types
         if (!$req_target->{'deliveryService'} && ( !$req_target->{'weight'} || !$req_target->{'order'} ) || ( $req_target->{'weight'} && $req_target->{'order'} ) ) {
            return $self->render(json => {"message" => "please provide a valid json for targets"}, status => 400);
         }
@@ -218,6 +222,7 @@ sub update() {
     foreach my $req_target (@{$req_targets}) {
         my $target_id = $valid_targets->{$req_target->{'deliveryService'}};
 
+        # TODO: update this to handle GEO-type targets
         if ($req_target->{'weight'}) {
             my $steering_target_row = $self->db->resultset('SteeringTarget')->find({ deliveryservice => $steering_id, target => $target_id});
             $steering_target_row->value($req_target->{weight});
diff --git a/traffic_ops/app/lib/API/DeliveryService/SteeringTarget.pm b/traffic_ops/app/lib/API/DeliveryService/SteeringTarget.pm
index 7ea4dee..88bdb15 100644
--- a/traffic_ops/app/lib/API/DeliveryService/SteeringTarget.pm
+++ b/traffic_ops/app/lib/API/DeliveryService/SteeringTarget.pm
@@ -308,6 +308,7 @@ sub is_target_valid {
 	# Validate the input against the rules
 	my $result = validate( $params, $rules );
 
+	# TODO: fix indentation below, validate GEO-types
 	if ( $result->{success} ) {
                 if ( ( $target_type eq "STEERING_WEIGHT" ) and ( $params->{value} < 0 ) ) {
 		    return ( 0, "Invalid value for target type STEERING_WEIGHT: can not be negative" );
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/cache/Cache.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/cache/Cache.java
index a9882ff..efd676a 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/cache/Cache.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/cache/Cache.java
@@ -27,6 +27,7 @@ import java.util.Map;
 import com.comcast.cdn.traffic_control.traffic_router.core.hash.DefaultHashable;
 import com.comcast.cdn.traffic_control.traffic_router.core.hash.Hashable;
 import com.comcast.cdn.traffic_control.traffic_router.core.util.JsonUtils;
+import com.comcast.cdn.traffic_control.traffic_router.geolocation.Geolocation;
 import com.fasterxml.jackson.databind.JsonNode;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
@@ -43,12 +44,18 @@ public class Cache implements Comparable<Cache>, Hashable<Cache> {
 	private List<InetRecord> ipAddresses;
 	private int port;
 	private final Map<String, DeliveryServiceReference> deliveryServices = new HashMap<String, DeliveryServiceReference>();
+	private final Geolocation geolocation;
 	private final Hashable hashable = new DefaultHashable();
 	private int httpsPort = 443;
 
-	public Cache(final String id, final String hashId, final int hashCount) {
+	public Cache(final String id, final String hashId, final int hashCount, final Geolocation geolocation) {
 		this.id = id;
 		hashable.generateHashes(hashId, hashCount > 0 ? hashCount : REPLICAS);
+		this.geolocation = geolocation;
+	}
+
+	public Cache(final String id, final String hashId, final int hashCount) {
+		this(id, hashId, hashCount, null);
 	}
 
 	@Override
@@ -74,6 +81,10 @@ public class Cache implements Comparable<Cache>, Hashable<Cache> {
 		return deliveryServices.values();
 	}
 
+	public Geolocation getGeolocation() {
+		return geolocation;
+	}
+
 	public String getFqdn() {
 		return fqdn;
 	}
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/config/ConfigHandler.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/config/ConfigHandler.java
index 6ab8fa1..90fc738 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/config/ConfigHandler.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/config/ConfigHandler.java
@@ -340,7 +340,7 @@ public class ConfigHandler {
 					hashId = jo.get("hashId").textValue();
 				}
 
-				final Cache cache = new Cache(node, hashId, JsonUtils.optInt(jo, "hashCount"));
+				final Cache cache = new Cache(node, hashId, JsonUtils.optInt(jo, "hashCount"), loc.getGeolocation());
 				cache.setFqdn(JsonUtils.getString(jo, "fqdn"));
 				cache.setPort(JsonUtils.getInt(jo, "port"));
 
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringGeolocationComparator.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringGeolocationComparator.java
new file mode 100644
index 0000000..5436dae
--- /dev/null
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringGeolocationComparator.java
@@ -0,0 +1,75 @@
+/*
+ *
+ * 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.
+ */
+
+package com.comcast.cdn.traffic_control.traffic_router.core.ds;
+
+import java.util.Comparator;
+
+import com.comcast.cdn.traffic_control.traffic_router.geolocation.Geolocation;
+
+// TODO: add unit tests for this class
+public class SteeringGeolocationComparator implements Comparator<SteeringResult> {
+
+    private final Geolocation clientLocation;
+
+    public SteeringGeolocationComparator(final Geolocation clientLocation) {
+        this.clientLocation = clientLocation;
+    }
+
+    @Override
+    public int compare(final SteeringResult result1, final SteeringResult result2) {
+        final Geolocation originGeo1 = result1.getSteeringTarget().getGeolocation();
+        final Geolocation originGeo2 = result2.getSteeringTarget().getGeolocation();
+
+        final Geolocation cacheGeo1 = result1.getCache().getGeolocation();
+        final Geolocation cacheGeo2 = result2.getCache().getGeolocation();
+
+        // null origin geolocations are considered greater than (i.e. farther away) than non-null origin geolocations
+        if (originGeo1 != null && originGeo2 == null) {
+            return -1;
+        }
+        if (originGeo1 == null && originGeo2 != null) {
+            return 1;
+        }
+        if (originGeo1 == null && originGeo2 == null) {
+            return 0;
+        }
+
+        // same cache and origin locations, prefer lower geoOrder
+        if (cacheGeo1.equals(cacheGeo2) && originGeo1.equals(originGeo2)) {
+            return Integer.compare(result1.getSteeringTarget().getGeoOrder(), result2.getSteeringTarget().getGeoOrder());
+        }
+
+        final double distanceFromClientToCache1 = clientLocation.getDistanceFrom(cacheGeo1);
+        final double distanceFromClientToCache2 = clientLocation.getDistanceFrom(cacheGeo2);
+
+        final double distanceFromCacheToOrigin1 = cacheGeo1.getDistanceFrom(originGeo1);
+        final double distanceFromCacheToOrigin2 = cacheGeo2.getDistanceFrom(originGeo2);
+
+        final double totalDistance1 = distanceFromClientToCache1 + distanceFromCacheToOrigin1;
+        final double totalDistance2 = distanceFromClientToCache2 + distanceFromCacheToOrigin2;
+
+        // different cache and origin locations, prefer shortest total distance
+        if (totalDistance1 != totalDistance2) {
+            // TODO: if the difference is smaller than a certain threshold, still prefer the closer edge even though distance is greater?
+            return Double.compare(totalDistance1, totalDistance2);
+        }
+
+        // total distance is equal, prefer the closest edge to the client
+        return Double.compare(distanceFromClientToCache1, distanceFromClientToCache2);
+
+    }
+
+}
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringRegistry.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringRegistry.java
index 17340ac..2bf197b 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringRegistry.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringRegistry.java
@@ -56,9 +56,15 @@ public class SteeringRegistry {
 		registry.putAll(newSteerings);
 		for (final Steering steering : steerings) {
 			for (final SteeringTarget target : steering.getTargets()) {
-				if (target.getWeight() > 0) {
+				if (target.getGeolocation() != null && target.getGeoOrder() != 0) {
+					LOGGER.info("Steering " + steering.getDeliveryService() + " target " + target.getDeliveryService() + " now has geolocation [" + target.getLatitude() + ", "  + target.getLongitude() + "] and geoOrder " + target.getGeoOrder());
+				} else if (target.getGeolocation() != null && target.getWeight() > 0) {
+					LOGGER.info("Steering " + steering.getDeliveryService() + " target " + target.getDeliveryService() + " now has geolocation [" + target.getLatitude() + ", "  + target.getLongitude() + "] and weight " + target.getWeight());
+				} else if (target.getGeolocation() != null) {
+					LOGGER.info("Steering " + steering.getDeliveryService() + " target " + target.getDeliveryService() + " now has geolocation [" + target.getLatitude() + ", "  + target.getLongitude() + "]");
+				} else if (target.getWeight() > 0) {
 					LOGGER.info("Steering " + steering.getDeliveryService() + " target " + target.getDeliveryService() + " now has weight " + target.getWeight());
-				} else if (target.getOrder() > 0 || target.getOrder() < 0) { // this target has a specific order set
+				} else if (target.getOrder() != 0) { // this target has a specific order set
 					LOGGER.info("Steering " + steering.getDeliveryService() + " target " + target.getDeliveryService() + " now has order " + target.getOrder());
 				} else {
 					LOGGER.info("Steering " + steering.getDeliveryService() + " target " + target.getDeliveryService() + " now has weight " + target.getWeight() + " and order " + target.getOrder());
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringResult.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringResult.java
new file mode 100644
index 0000000..b86ccbd
--- /dev/null
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringResult.java
@@ -0,0 +1,54 @@
+/*
+ *
+ * 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.
+ */
+
+package com.comcast.cdn.traffic_control.traffic_router.core.ds;
+
+import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache;
+
+public class SteeringResult {
+    private SteeringTarget steeringTarget;
+    private DeliveryService deliveryService;
+    private Cache cache;
+
+    public SteeringResult(final SteeringTarget steeringTarget, final DeliveryService deliveryService) {
+        this.steeringTarget = steeringTarget;
+        this.deliveryService = deliveryService;
+    }
+
+    public SteeringTarget getSteeringTarget() {
+        return steeringTarget;
+    }
+
+    public void setSteeringTarget(final SteeringTarget steeringTarget) {
+        this.steeringTarget = steeringTarget;
+    }
+
+    public DeliveryService getDeliveryService() {
+        return deliveryService;
+    }
+
+    public void setDeliveryService(final DeliveryService deliveryService) {
+        this.deliveryService = deliveryService;
+    }
+
+    public Cache getCache() {
+        return cache;
+    }
+
+    public void setCache(final Cache cache) {
+        this.cache = cache;
+    }
+
+}
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringTarget.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringTarget.java
index ba400ac..d1a38df 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringTarget.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/SteeringTarget.java
@@ -16,15 +16,29 @@
 package com.comcast.cdn.traffic_control.traffic_router.core.ds;
 
 import com.comcast.cdn.traffic_control.traffic_router.core.hash.DefaultHashable;
+import com.comcast.cdn.traffic_control.traffic_router.geolocation.Geolocation;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
 public class SteeringTarget extends DefaultHashable {
+
+	private static final double DEFAULT_LAT = 0.0;
+	private static final double DEFAULT_LON = 0.0;
+
 	@JsonProperty
 	private String deliveryService;
 	@JsonProperty
 	private int weight;
 	@JsonProperty
 	private int order = 0;
+	@JsonProperty
+	private int geoOrder = 0;
+	// TODO: lat/long should probably be moved to the DeliveryService itself, but this is ok for a start
+	@JsonProperty
+	private double latitude = DEFAULT_LAT;
+	@JsonProperty
+	private double longitude = DEFAULT_LON;
+
+	private Geolocation geolocation;
 
 	public DefaultHashable generateHashes() {
 		return generateHashes(deliveryService, weight);
@@ -54,6 +68,40 @@ public class SteeringTarget extends DefaultHashable {
 		return order;
 	}
 
+	public void setGeoOrder(final int geoOrder) {
+		this.geoOrder = geoOrder;
+	}
+
+	public int getGeoOrder() {
+		return geoOrder;
+	}
+
+	public void setLatitude(final double latitude) {
+		this.latitude = latitude;
+	}
+
+	public double getLatitude() {
+		return latitude;
+	}
+
+	public void setLongitude(final double longitude) {
+		this.longitude = longitude;
+	}
+
+	public double getLongitude() {
+		return longitude;
+	}
+
+	public Geolocation getGeolocation() {
+		if (geolocation != null) {
+			return geolocation;
+		}
+		if (latitude != DEFAULT_LAT && longitude != DEFAULT_LON) {
+			geolocation = new Geolocation(latitude, longitude);
+		}
+		return geolocation;
+	}
+
 	@Override
 	@SuppressWarnings("PMD")
 	public boolean equals(Object o) {
@@ -64,6 +112,9 @@ public class SteeringTarget extends DefaultHashable {
 
 		if (weight != target.weight) return false;
 		if (order != target.order) return false;
+		if (geoOrder != target.geoOrder) return false;
+		if (latitude != target.latitude) return false;
+		if (longitude != target.longitude) return false;
 		return deliveryService != null ? deliveryService.equals(target.deliveryService) : target.deliveryService == null;
 
 	}
@@ -73,6 +124,9 @@ public class SteeringTarget extends DefaultHashable {
 		int result = deliveryService != null ? deliveryService.hashCode() : 0;
 		result = 31 * result + weight;
 		result = 31 * result + order;
+		result = 31 * result + geoOrder;
+		result = 31 * result + (int) latitude;
+		result = 31 * result + (int) longitude;
 		return result;
 	}
 }
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouter.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouter.java
index caaf4e8..5f49865 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouter.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouter.java
@@ -22,6 +22,7 @@ import java.net.URL;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -31,6 +32,7 @@ import java.util.Set;
 import java.util.stream.Collectors;
 
 import com.comcast.cdn.traffic_control.traffic_router.configuration.ConfigurationListener;
+import com.comcast.cdn.traffic_control.traffic_router.core.ds.SteeringResult;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.SteeringTarget;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.Steering;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.SteeringRegistry;
@@ -51,6 +53,7 @@ import com.comcast.cdn.traffic_control.traffic_router.core.cache.InetRecord;
 import com.comcast.cdn.traffic_control.traffic_router.core.dns.ZoneManager;
 import com.comcast.cdn.traffic_control.traffic_router.core.dns.DNSAccessRecord;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.DeliveryService;
+import com.comcast.cdn.traffic_control.traffic_router.core.ds.SteeringGeolocationComparator;
 import com.comcast.cdn.traffic_control.traffic_router.core.loc.FederationRegistry;
 import com.comcast.cdn.traffic_control.traffic_router.geolocation.Geolocation;
 import com.comcast.cdn.traffic_control.traffic_router.geolocation.GeolocationException;
@@ -510,36 +513,38 @@ public class TrafficRouter {
 	public HTTPRouteResult multiRoute(final HTTPRequest request, final Track track) throws MalformedURLException, GeolocationException {
 		final DeliveryService entryDeliveryService = cacheRegister.getDeliveryService(request, true);
 
-		if (isTlsMismatch(request, entryDeliveryService)) {
-			track.setResult(ResultType.ERROR);
-			track.setResultDetails(ResultDetails.DS_TLS_MISMATCH);
+		final List<SteeringResult> steeringResults = getSteeringResults(request, track, entryDeliveryService);
+
+		if (steeringResults == null) {
 			return null;
 		}
 
 		final HTTPRouteResult routeResult = new HTTPRouteResult(true);
-		final List<DeliveryService> deliveryServices = getDeliveryServices(request, track, entryDeliveryService);
+		routeResult.setDeliveryService(entryDeliveryService);
 
-		if (deliveryServices == null) {
-			return null;
-		}
+		final List<SteeringResult> resultsToRemove = new ArrayList<>();
 
-		routeResult.setDeliveryService(entryDeliveryService);
-		for (final DeliveryService deliveryService : deliveryServices) {
-			if (deliveryService.isRegionalGeoEnabled()) {
-				LOGGER.error("Regional Geo Blocking is not supported with multi-route delivery services.. skipping " + entryDeliveryService.getId() + "/" + deliveryService.getId());
-				continue;
-			}
+		for (final SteeringResult steeringResult : steeringResults) {
+			final DeliveryService ds = steeringResult.getDeliveryService();
 
-			if (deliveryService.isAvailable()) {
-				final List<Cache> caches = selectCaches(request, deliveryService, track);
+			final List<Cache> caches = selectCaches(request, ds, track);
 
-				if (caches != null && !caches.isEmpty()) {
-					final Cache cache = consistentHasher.selectHashable(caches, deliveryService.getDispersion(), request.getPath());
-					routeResult.addUrl(new URL(deliveryService.createURIString(request, cache)));
-				}
+			if (caches != null && !caches.isEmpty()) {
+				final Cache cache = consistentHasher.selectHashable(caches, ds.getDispersion(), request.getPath());
+				steeringResult.setCache(cache);
+			} else {
+				resultsToRemove.add(steeringResult);
 			}
 		}
 
+		steeringResults.removeAll(resultsToRemove);
+
+		geoSortSteeringResults(steeringResults, request.getClientIP(), entryDeliveryService);
+
+		for (final SteeringResult steeringResult: steeringResults) {
+			routeResult.addUrl(new URL(steeringResult.getDeliveryService().createURIString(request, steeringResult.getCache())));
+		}
+
 		if (routeResult.getUrls().isEmpty()) {
 			routeResult.addUrl(entryDeliveryService.getFailureHttpResponse(request, track));
 		}
@@ -612,24 +617,41 @@ public class TrafficRouter {
 		return routeResult;
 	}
 
-	private List<DeliveryService> getDeliveryServices(final HTTPRequest request, final Track track, final DeliveryService entryDeliveryService) {
-		final List<DeliveryService> deliveryServices = consistentHashMultiDeliveryService(entryDeliveryService, request.getPath());
+	private List<SteeringResult> getSteeringResults(final HTTPRequest request, final Track track, final DeliveryService entryDeliveryService) {
+
+		if (isTlsMismatch(request, entryDeliveryService)) {
+			track.setResult(ResultType.ERROR);
+			track.setResultDetails(ResultDetails.DS_TLS_MISMATCH);
+			return null;
+		}
+
+		final List<SteeringResult> steeringResults = consistentHashMultiDeliveryService(entryDeliveryService, request.getPath());
 
-		if (deliveryServices == null || deliveryServices.isEmpty()) {
+		if (steeringResults == null || steeringResults.isEmpty()) {
 			track.setResult(ResultType.DS_MISS);
 			track.setResultDetails(ResultDetails.DS_NOT_FOUND);
 			return null;
 		}
 
-		for (final DeliveryService deliveryService : deliveryServices) {
-			if (isTlsMismatch(request, deliveryService)) {
+		List<SteeringResult> toBeRemoved = new ArrayList<>();
+		for (final SteeringResult steeringResult : steeringResults) {
+			final DeliveryService ds = steeringResult.getDeliveryService();
+			if (isTlsMismatch(request, ds)) {
 				track.setResult(ResultType.ERROR);
 				track.setResultDetails(ResultDetails.DS_TLS_MISMATCH);
 				return null;
 			}
+			if (ds.isRegionalGeoEnabled()) {
+				LOGGER.error("Regional Geo Blocking is not supported with multi-route delivery services.. skipping " + entryDeliveryService.getId() + "/" + ds.getId());
+				toBeRemoved.add(steeringResult);
+			} else if (!ds.isAvailable()) {
+				toBeRemoved.add(steeringResult);
+			}
+
 		}
 
-		return deliveryServices;
+		steeringResults.removeAll(toBeRemoved);
+		return steeringResults.isEmpty() ? null : steeringResults;
 	}
 
 	private DeliveryService getDeliveryService(final HTTPRequest request, final Track track) {
@@ -815,10 +837,6 @@ public class TrafficRouter {
 		return consistentHasher.selectHashable(caches, deliveryService.getDispersion(), requestPath);
 	}
 
-	public DeliveryService consistentHashDeliveryService(final String deliveryServiceId, final String requestPath) {
-		return consistentHashDeliveryService(cacheRegister.getDeliveryService(deliveryServiceId), requestPath, "");
-	}
-
 	private boolean isSteeringDeliveryService(final DeliveryService deliveryService) {
 		return deliveryService != null && steeringRegistry.has(deliveryService.getId());
 	}
@@ -833,16 +851,45 @@ public class TrafficRouter {
 		return steeringRegistry.get(deliveryService.getId()).isClientSteering();
 	}
 
-	public List<DeliveryService> consistentHashMultiDeliveryService(final DeliveryService deliveryService, final String requestPath) {
+	private Geolocation getClientLocationByCoverageZoneOrGeo(final String clientIP, final DeliveryService deliveryService) {
+		Geolocation clientLocation;
+		final NetworkNode networkNode = getNetworkNode(clientIP);
+		if (networkNode != null && networkNode.getGeolocation() != null) {
+			clientLocation = networkNode.getGeolocation();
+		} else {
+			try {
+				clientLocation = getLocation(clientIP, deliveryService);
+			} catch (GeolocationException e) {
+				clientLocation = null;
+			}
+		}
+		return deliveryService.supportLocation(clientLocation);
+	}
+
+	// TODO: add unit test for this method
+	private void geoSortSteeringResults(final List<SteeringResult> steeringResults, final String clientIP, final DeliveryService deliveryService) {
+		if (clientIP == null || clientIP.isEmpty()
+				|| steeringResults.stream().allMatch(t -> t.getSteeringTarget().getGeolocation() == null)) {
+			return;
+		}
+
+		final Geolocation clientLocation = getClientLocationByCoverageZoneOrGeo(clientIP, deliveryService);
+		if (clientLocation != null) {
+			Collections.sort(steeringResults, new SteeringGeolocationComparator(clientLocation));
+			Collections.sort(steeringResults, Comparator.comparingInt(s -> s.getSteeringTarget().getOrder())); // re-sort by order to preserve the ordering done by ConsistentHasher
+		}
+	}
+
+	public List<SteeringResult> consistentHashMultiDeliveryService(final DeliveryService deliveryService, final String requestPath) {
 		if (deliveryService == null) {
 			return null;
 		}
 
-		final List<DeliveryService> deliveryServices = new ArrayList<DeliveryService>();
+		final List<SteeringResult> steeringResults = new ArrayList<>();
 
 		if (!isSteeringDeliveryService(deliveryService)) {
-			deliveryServices.add(deliveryService);
-			return deliveryServices;
+			steeringResults.add(new SteeringResult(null, deliveryService));
+			return steeringResults;
 		}
 
 		final Steering steering = steeringRegistry.get(deliveryService.getId());
@@ -852,11 +899,15 @@ public class TrafficRouter {
 			final DeliveryService target = cacheRegister.getDeliveryService(steeringTarget.getDeliveryService());
 
 			if (target != null) { // target might not be in CRConfig yet
-				deliveryServices.add(target);
+				steeringResults.add(new SteeringResult(steeringTarget, target));
 			}
 		}
 
-		return deliveryServices;
+		return steeringResults;
+	}
+
+	public DeliveryService consistentHashDeliveryService(final String deliveryServiceId, final String requestPath) {
+		return consistentHashDeliveryService(cacheRegister.getDeliveryService(deliveryServiceId), requestPath, "");
 	}
 
 	public DeliveryService consistentHashDeliveryService(final DeliveryService deliveryService, final String requestPath, final String xtcSteeringOption) {

-- 
To stop receiving notification emails like this one, please contact
elsloo@apache.org.