You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by da...@apache.org on 2019/06/04 21:29:34 UTC

[trafficcontrol] branch master updated: Client steering forced diversity (#3573)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 7e4050d  Client steering forced diversity (#3573)
7e4050d is described below

commit 7e4050d3299342dd256a3c8f9abb2ad5851bd400
Author: Rawlin Peters <ra...@comcast.com>
AuthorDate: Tue Jun 4 15:29:29 2019 -0600

    Client steering forced diversity (#3573)
    
    * Add TR support for client.steering.forced.diversity feature
    
    * Add test to verify client.steering.forced.diversity support
    
    * Add docs and changelog for client.steering.forced.diversity
    
    * Modify client steering diversity logic
    
    Since the cache assignments can be different between targets, just
    remove any already-selected caches from the list of caches to choose
    from. If the resulting list is empty, then just choose from all
    available caches for the target.
    
    * CSFD: select from regular CG when deep CG caches have been selected
    
    * Ensure client_steering deep CZF hits still tracked as DEEP_CZ
    
    Even though forced-diversity causes a regular CZF lookup for initial
    deep CZF hits, that request should still be tracked as a deep hit.
    
    * Update CSD external test to cover the deep cachegroup case
---
 CHANGELOG.md                                       |   1 +
 docs/source/admin/traffic_ops/using.rst            |   2 +
 .../traffic_router/core/router/TrafficRouter.java  |  36 +-
 .../traffic_router/core/external/SteeringTest.java |  63 ++++
 .../core/router/TrafficRouterTest.java             |   1 +
 traffic_router/core/src/test/resources/dczmap.json |  12 +
 .../test/resources/internal/api/1.3/steering.json  |  26 ++
 .../core/src/test/resources/publish/CrConfig.json  | 372 +++++++++++++++++++++
 .../core/src/test/resources/publish/CrStates       |  29 +-
 9 files changed, 538 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3948912..bd9f5d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - Traffic Router: TR now generates a self-signed certificate at startup and uses it as the default TLS cert.
   The default certificate is used whenever a client attempts an SSL handshake for an SNI host which does not match
   any of the other certificates.
+- Client Steering Forced Diversity: force Traffic Router to return more unique edge caches in CLIENT_STEERING results instead of the default behavior which can sometimes return a result of multiple targets using the same edge cache. In the case of edge cache failures, this feature will give clients a chance to retry a different edge cache. This can be enabled with the new "client.steering.forced.diversity" Traffic Router profile parameter.
 - Traffic Ops Golang Endpoints
   - /api/1.4/deliveryservices `(GET,POST,PUT)`
   - /api/1.4/users `(GET,POST,PUT)`
diff --git a/docs/source/admin/traffic_ops/using.rst b/docs/source/admin/traffic_ops/using.rst
index 8e444a4..6dc0f63 100644
--- a/docs/source/admin/traffic_ops/using.rst
+++ b/docs/source/admin/traffic_ops/using.rst
@@ -413,6 +413,8 @@ Traffic Router Profile
 +-----------------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------+
 | deepcoveragezone.polling.url            | CRConfig.json          | The location (URL) to retrieve the deep coverage zone map file in JSON format from.                                                              |
 +-----------------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------+
+| client.steering.forced.diversity        | CRConfig.json          | Enable the Client Steering Forced Diversity feature (value = "true") to diversify CLIENT_STEERING results by including more unique edge caches   |
++-----------------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------+
 | tld.soa.expire                          | CRConfig.json          | The value for the expire field the Traffic Router DNS Server will respond with on Start of Authority (SOA) records.                              |
 +-----------------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------+
 | tld.soa.minimum                         | CRConfig.json          | The value for the minimum field the Traffic Router DNS Server will respond with on SOA records.                                                  |
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 4ba3e99..a58cdef 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
@@ -81,6 +81,7 @@ import com.comcast.cdn.traffic_control.traffic_router.core.loc.AnonymousIpDataba
 public class TrafficRouter {
 	public static final Logger LOGGER = Logger.getLogger(TrafficRouter.class);
 	public static final String XTC_STEERING_OPTION = "x-tc-steering-option";
+	public static final String CLIENT_STEERING_DIVERSITY = "client.steering.forced.diversity";
 
 	private final CacheRegister cacheRegister;
 	private final ZoneManager zoneManager;
@@ -89,6 +90,7 @@ public class TrafficRouter {
 	private final AnonymousIpDatabaseService anonymousIpService;
 	private final FederationRegistry federationRegistry;
 	private final boolean consistentDNSRouting;
+	private final boolean clientSteeringDiversityEnabled;
 
 	private final Random random = new Random(System.nanoTime());
 	private Set<String> requestHeaders = new HashSet<String>();
@@ -114,6 +116,7 @@ public class TrafficRouter {
 		this.anonymousIpService = anonymousIpService;
 		this.federationRegistry = federationRegistry;
 		this.consistentDNSRouting = JsonUtils.optBoolean(cr.getConfig(), "consistent.dns.routing");
+		this.clientSteeringDiversityEnabled = JsonUtils.optBoolean(cr.getConfig(), CLIENT_STEERING_DIVERSITY);
 		this.zoneManager = new ZoneManager(this, statTracker, trafficOpsUtils, trafficRouterManager);
 
 		if (cr.getConfig() != null) {
@@ -279,11 +282,15 @@ public class TrafficRouter {
 		return null;
 	}
 
-	@SuppressWarnings("PMD.CyclomaticComplexity")
 	protected List<Cache> selectCaches(final HTTPRequest request, final DeliveryService ds, final Track track) throws GeolocationException {
+		return selectCaches(request, ds, track, true);
+	}
+
+	@SuppressWarnings("PMD.CyclomaticComplexity")
+	protected List<Cache> selectCaches(final HTTPRequest request, final DeliveryService ds, final Track track, final boolean enableDeep) throws GeolocationException {
 		CacheLocation cacheLocation;
 		ResultType result = ResultType.CZ;
-		final boolean useDeep = (ds.getDeepCache() == DeliveryService.DeepCachingType.ALWAYS);
+		final boolean useDeep = enableDeep && (ds.getDeepCache() == DeliveryService.DeepCachingType.ALWAYS);
 
 		if (useDeep) {
 			// Deep caching is enabled. See if there are deep caches available
@@ -523,6 +530,7 @@ public class TrafficRouter {
 	 * @param track A {@link Track} object used to track routing statistics
 	 * @return The list of routes available to service the client's request.
 	 */
+	@SuppressWarnings("PMD.CyclomaticComplexity")
 	public HTTPRouteResult multiRoute(final HTTPRequest request, final Track track) throws MalformedURLException, GeolocationException {
 		final DeliveryService entryDeliveryService = cacheRegister.getDeliveryService(request, true);
 
@@ -537,19 +545,37 @@ public class TrafficRouter {
 
 		final List<SteeringResult> resultsToRemove = new ArrayList<>();
 
+		final Set<Cache> selectedCaches = new HashSet<>();
+
 		// Pattern based consistent hashing - use consistentHashRegex from steering DS instead of targets
 		final String steeringHash = buildPatternBasedHashString(entryDeliveryService.getConsistentHashRegex(), request.getPath());
 		for (final SteeringResult steeringResult : steeringResults) {
 			final DeliveryService ds = steeringResult.getDeliveryService();
 
-			final List<Cache> caches = selectCaches(request, ds, track);
+			List<Cache> caches = selectCaches(request, ds, track);
 
 			// child Delivery Services can use their query parameters
 			final String pathToHash = steeringHash + ds.extractSignificantQueryParams(request);
 
 			if (caches != null && !caches.isEmpty()) {
+				if (isClientSteeringDiversityEnabled()) {
+					List<Cache> tryCaches = new ArrayList<>(caches);
+					tryCaches.removeAll(selectedCaches);
+					if (!tryCaches.isEmpty()) {
+						caches = tryCaches;
+					} else if (track.result == ResultType.DEEP_CZ) {
+						// deep caches have been selected already, try non-deep selection
+						tryCaches = selectCaches(request, ds, track, false);
+						track.setResult(ResultType.DEEP_CZ); // request should still be tracked as a DEEP_CZ hit
+						tryCaches.removeAll(selectedCaches);
+						if (!tryCaches.isEmpty()) {
+							caches = tryCaches;
+						}
+					}
+				}
 				final Cache cache = consistentHasher.selectHashable(caches, ds.getDispersion(), pathToHash);
 				steeringResult.setCache(cache);
+				selectedCaches.add(cache);
 			} else {
 				resultsToRemove.add(steeringResult);
 			}
@@ -1226,6 +1252,10 @@ public class TrafficRouter {
 		return consistentDNSRouting;
 	}
 
+	public boolean isClientSteeringDiversityEnabled() {
+		return clientSteeringDiversityEnabled;
+	}
+
 	private List<Cache> enforceGeoRedirect(final Track track, final DeliveryService ds, final String clientIp, final Geolocation queriedClientLocation) {
 		final String urlType = ds.getGeoRedirectUrlType();
 		track.setResult(ResultType.GEO_REDIRECT);
diff --git a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/external/SteeringTest.java b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/external/SteeringTest.java
index 7342670..0986623 100644
--- a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/external/SteeringTest.java
+++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/external/SteeringTest.java
@@ -37,11 +37,14 @@ import org.junit.runners.MethodSorters;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
+import java.util.Set;
 
 import static org.hamcrest.Matchers.endsWith;
 import static org.hamcrest.Matchers.greaterThan;
@@ -553,4 +556,64 @@ public class SteeringTest {
 			if (response != null) { response.close(); }
 		}
 	}
+
+	@Test
+	public void itSupportsClientSteeringDiversity() throws Exception {
+		final String path = "/foo?fakeClientIpAddress=192.168.42.10"; // this IP should get a DEEP_CZ hit (via dczmap.json)
+		HttpGet httpGet = new HttpGet("http://localhost:" + routerHttpPort + path);
+		httpGet.addHeader("Host", "cdn.client-steering-diversity-test.thecdn.example.com");
+
+		CloseableHttpResponse response = null;
+
+		try {
+			response = httpClient.execute(httpGet);
+
+			HttpEntity entity = response.getEntity();
+			assertThat("Failed getting 302 for request " + httpGet.getFirstHeader("Host").getValue(), response.getStatusLine().getStatusCode(), equalTo(302));
+
+			ObjectMapper objectMapper = new ObjectMapper(new JsonFactory());
+
+			assertThat(entity.getContent(), not(nullValue()));
+
+			JsonNode json = objectMapper.readTree(entity.getContent());
+
+			assertThat(json.has("locations"), equalTo(true));
+			assertThat(json.get("locations").size(), equalTo(5));
+
+			List<String> actualEdgesList = new ArrayList<>();
+			Set<String> actualTargets = new HashSet<>();
+
+			for (JsonNode n : json.get("locations")) {
+				String l = n.asText();
+				l = l.replaceFirst("http://", "");
+				String[] parts = l.split("\\.");
+				actualEdgesList.add(parts[0]);
+				actualTargets.add(parts[1]);
+			}
+
+			// assert that:
+			// - 1st and 2nd targets are edges from the deep cachegroup (because this is a deep hit)
+			// - 3rd target is the last unselected edge, which is *not* in the deep cachegroup
+			//   (because once all the deep edges have been selected, we select from the regular cachegroup)
+			// - 4th and 5th targets are any of the three edges (because all available edges have already been selected)
+			Set<String> deepEdges = new HashSet<>();
+			deepEdges.add("edge-cache-csd-1");
+			deepEdges.add("edge-cache-csd-2");
+			Set<String> allEdges = new HashSet<>(deepEdges);
+			allEdges.add("edge-cache-csd-3");
+			assertThat(actualEdgesList.get(0), isIn(deepEdges));
+			assertThat(actualEdgesList.get(1), isIn(deepEdges));
+			assertThat(actualEdgesList.get(2), equalTo("edge-cache-csd-3"));
+			assertThat(actualEdgesList.get(3), isIn(allEdges));
+			assertThat(actualEdgesList.get(4), isIn(allEdges));
+
+			// assert that all 5 steering targets are included in the response
+			String[] expectedTargetsArray = {"csd-target-1", "csd-target-2", "csd-target-3", "csd-target-4", "csd-target-5"};
+			Set<String> expectedTargets = new HashSet<>(Arrays.asList(expectedTargetsArray));
+			assertThat(actualTargets, equalTo(expectedTargets));
+
+		} finally {
+			if (response != null) { response.close(); }
+		}
+	}
 }
diff --git a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouterTest.java b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouterTest.java
index 16d6fe2..ad8abd2 100644
--- a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouterTest.java
+++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouterTest.java
@@ -170,6 +170,7 @@ public class TrafficRouterTest {
         when(deliveryService.filterAvailableLocations(any(Collection.class))).thenCallRealMethod();
 
         when(trafficRouter.selectCaches(any(HTTPRequest.class), any(DeliveryService.class), any(Track.class))).thenCallRealMethod();
+        when(trafficRouter.selectCaches(any(HTTPRequest.class), any(DeliveryService.class), any(Track.class), anyBoolean())).thenCallRealMethod();
         when(trafficRouter.selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class))).thenCallRealMethod();
 
         Geolocation clientLocation = new Geolocation(40, -100);
diff --git a/traffic_router/core/src/test/resources/dczmap.json b/traffic_router/core/src/test/resources/dczmap.json
index bfc5c5b..9d46750 100644
--- a/traffic_router/core/src/test/resources/dczmap.json
+++ b/traffic_router/core/src/test/resources/dczmap.json
@@ -20,6 +20,18 @@
 				"host3",
 				"host4"
 			]
+		},
+		"csd-group": {
+			"network6": [
+				"fd12:3456::/64"
+			],
+			"network": [
+				"192.168.42.0/24"
+			],
+			"caches": [
+				"edge-cache-csd-1",
+				"edge-cache-csd-2"
+			]
 		}
 	}
 }
diff --git a/traffic_router/core/src/test/resources/internal/api/1.3/steering.json b/traffic_router/core/src/test/resources/internal/api/1.3/steering.json
index ccb9a31..f522933 100644
--- a/traffic_router/core/src/test/resources/internal/api/1.3/steering.json
+++ b/traffic_router/core/src/test/resources/internal/api/1.3/steering.json
@@ -45,6 +45,32 @@
       "clientSteering": true
     },
     {
+      "deliveryService": "client-steering-diversity-test",
+      "targets": [
+        {
+          "deliveryService": "csd-target-1",
+          "weight": "5000"
+        },
+        {
+          "deliveryService": "csd-target-2",
+          "weight": "5000"
+        },
+        {
+          "deliveryService": "csd-target-3",
+          "weight": "5000"
+        },
+        {
+          "deliveryService": "csd-target-4",
+          "weight": "5000"
+        },
+        {
+          "deliveryService": "csd-target-5",
+          "weight": "5000"
+        }
+      ],
+      "clientSteering": true
+    },
+    {
       "deliveryService": "client-steering-test-2",
       "targets": [
         {
diff --git a/traffic_router/core/src/test/resources/publish/CrConfig.json b/traffic_router/core/src/test/resources/publish/CrConfig.json
index e3cdaf0..b41a917 100644
--- a/traffic_router/core/src/test/resources/publish/CrConfig.json
+++ b/traffic_router/core/src/test/resources/publish/CrConfig.json
@@ -479,6 +479,69 @@
       "deliveryServices": {
         "http-and-https-test": [ "edge-cache-082.http-and-https-test.thecdn.example.com"]
       }
+    },
+    "edge-cache-csd-1": {
+      "type": "EDGE",
+      "profile": "EDGE_STATIC",
+      "status": "REPORTED",
+      "hashId": "edge-cache-csd-1@xmpp.cdn.example.com",
+      "fqdn": "edge-cache-csd-1.thecdn.example.com",
+      "port": "8090",
+      "ip": "12.34.8.201",
+      "ip6": "2001:dead:beef:8:C::81/64",
+      "cacheGroup": "cache-group-csd",
+      "locationId": "location-csd",
+      "hashCount": 1000,
+      "interfaceName": "bond0",
+      "deliveryServices": {
+        "csd-target-1": ["edge-cache-csd-1.csd-target-1.thecdn.example.com"],
+        "csd-target-2": ["edge-cache-csd-1.csd-target-2.thecdn.example.com"],
+        "csd-target-3": ["edge-cache-csd-1.csd-target-3.thecdn.example.com"],
+        "csd-target-4": ["edge-cache-csd-1.csd-target-4.thecdn.example.com"],
+        "csd-target-5": ["edge-cache-csd-1.csd-target-5.thecdn.example.com"]
+      }
+    },
+    "edge-cache-csd-2": {
+      "type": "EDGE",
+      "profile": "EDGE_STATIC",
+      "status": "REPORTED",
+      "hashId": "edge-cache-csd-2@xmpp.cdn.example.com",
+      "fqdn": "edge-cache-csd-2.thecdn.example.com",
+      "port": "8090",
+      "ip": "12.34.8.202",
+      "ip6": "2001:dead:beef:8:C::82/64",
+      "cacheGroup": "cache-group-csd",
+      "locationId": "location-csd",
+      "hashCount": 1000,
+      "interfaceName": "bond0",
+      "deliveryServices": {
+        "csd-target-1": ["edge-cache-csd-2.csd-target-1.thecdn.example.com"],
+        "csd-target-2": ["edge-cache-csd-2.csd-target-2.thecdn.example.com"],
+        "csd-target-3": ["edge-cache-csd-2.csd-target-3.thecdn.example.com"],
+        "csd-target-4": ["edge-cache-csd-2.csd-target-4.thecdn.example.com"],
+        "csd-target-5": ["edge-cache-csd-2.csd-target-5.thecdn.example.com"]
+      }
+    },
+    "edge-cache-csd-3": {
+      "type": "EDGE",
+      "profile": "EDGE_STATIC",
+      "status": "REPORTED",
+      "hashId": "edge-cache-csd-3@xmpp.cdn.example.com",
+      "fqdn": "edge-cache-csd-3.thecdn.example.com",
+      "port": "8090",
+      "ip": "12.34.8.203",
+      "ip6": "2001:dead:beef:8:C::83/64",
+      "cacheGroup": "cache-group-csd",
+      "locationId": "location-csd",
+      "hashCount": 1000,
+      "interfaceName": "bond0",
+      "deliveryServices": {
+        "csd-target-1": ["edge-cache-csd-3.csd-target-1.thecdn.example.com"],
+        "csd-target-2": ["edge-cache-csd-3.csd-target-2.thecdn.example.com"],
+        "csd-target-3": ["edge-cache-csd-3.csd-target-3.thecdn.example.com"],
+        "csd-target-4": ["edge-cache-csd-3.csd-target-4.thecdn.example.com"],
+        "csd-target-5": ["edge-cache-csd-3.csd-target-5.thecdn.example.com"]
+      }
     }
   },
   "contentRouters": {
@@ -666,6 +729,308 @@
       "geolocationProvider": "maxmindGeolocationService",
       "staticDnsEntries": []
     },
+    "client-steering-diversity-test": {
+      "sslEnabled": "false",
+      "protocol": {
+        "acceptHttp" : "true",
+        "acceptHttps" : "false",
+        "redirectToHttps" : "false"
+      },
+      "requestHeaders": ["X-MoneyTrace"],
+      "domains": ["client-steering-diversity-test.thecdn.example.com"],
+      "missLocation": {
+        "long": "-87.627778",
+        "lat": "41.881944"
+      },
+      "ttls": {
+        "AAAA": "3600",
+        "SOA": "7200",
+        "A": "3600",
+        "NS": "3600"
+      },
+      "ttl": "3600",
+      "ip6RoutingEnabled": "true",
+      "bypassDestination": {"HTTP": {
+        "port": "80",
+        "fqdn": "client-steering-diversity-test.overflowcdn.net"
+      }},
+      "dispersion": {
+        "limit": 1,
+        "shuffled": "true"
+      },
+      "coverageZoneOnly": "false",
+      "routingName": "cdn",
+      "regionalGeoBlocking": "false",
+      "matchsets": [{
+        "protocol": "HTTP",
+        "matchlist": [{
+          "regex": ".*\\.client-steering-diversity-test\\..*",
+          "match-type": "HOST"
+        }]
+      }],
+      "soa": {
+        "expire": "604800",
+        "minimum": "30",
+        "admin": "operations@thecdn.example.com",
+        "retry": "7200",
+        "refresh": "28800"
+      },
+      "geolocationProvider": "maxmindGeolocationService",
+      "staticDnsEntries": []
+    },
+    "csd-target-1": {
+      "sslEnabled": "false",
+      "protocol": {
+        "acceptHttp" : "true",
+        "acceptHttps" : "false",
+        "redirectToHttps" : "false"
+      },
+      "requestHeaders": [
+        "User-Agent",
+        "MyHeader",
+        "Date"
+      ],
+      "deepCachingType": "ALWAYS",
+      "dispersion": {
+        "limit": 1,
+        "shuffled": "true"
+      },
+      "domains": ["csd-target-1.thecdn.example.com"],
+      "coverageZoneOnly": "false",
+      "routingName": "cdn",
+      "matchsets": [
+        {
+          "protocol": "HTTP",
+          "matchlist": [{
+            "regex": ".*\\.csd-target-1\\..*",
+            "match-type": "HOST"
+          }]
+        }
+      ],
+      "regionalGeoBlocking": "false",
+      "ttls": {
+        "AAAA": "3600",
+        "SOA": "7200",
+        "A": "3600",
+        "NS": "3600"
+      },
+      "missLocation": {
+        "long": "-87.627778",
+        "lat": "41.881944"
+      },
+      "soa": {
+        "expire": "604800",
+        "minimum": "3 0",
+        "admin": "operations@thecdn.example.com",
+        "retry": "7200",
+        "refresh": "28800"
+      },
+      "geolocationProvider": "maxmindGeolocationService",
+      "ttl": "3600",
+      "ip6RoutingEnabled": "false"
+    },
+    "csd-target-2": {
+      "sslEnabled": "false",
+      "protocol": {
+        "acceptHttp" : "true",
+        "acceptHttps" : "false",
+        "redirectToHttps" : "false"
+      },
+      "requestHeaders": [
+        "User-Agent",
+        "MyHeader",
+        "Date"
+      ],
+      "deepCachingType": "ALWAYS",
+      "dispersion": {
+        "limit": 1,
+        "shuffled": "true"
+      },
+      "domains": ["csd-target-2.thecdn.example.com"],
+      "coverageZoneOnly": "false",
+      "routingName": "cdn",
+      "matchsets": [
+        {
+          "protocol": "HTTP",
+          "matchlist": [{
+            "regex": ".*\\.csd-target-2\\..*",
+            "match-type": "HOST"
+          }]
+        }
+      ],
+      "regionalGeoBlocking": "false",
+      "ttls": {
+        "AAAA": "3600",
+        "SOA": "7200",
+        "A": "3600",
+        "NS": "3600"
+      },
+      "missLocation": {
+        "long": "-87.627778",
+        "lat": "41.881944"
+      },
+      "soa": {
+        "expire": "604800",
+        "minimum": "3 0",
+        "admin": "operations@thecdn.example.com",
+        "retry": "7200",
+        "refresh": "28800"
+      },
+      "geolocationProvider": "maxmindGeolocationService",
+      "ttl": "3600",
+      "ip6RoutingEnabled": "false"
+    },
+    "csd-target-3": {
+      "sslEnabled": "false",
+      "protocol": {
+        "acceptHttp" : "true",
+        "acceptHttps" : "false",
+        "redirectToHttps" : "false"
+      },
+      "requestHeaders": [
+        "User-Agent",
+        "MyHeader",
+        "Date"
+      ],
+      "deepCachingType": "ALWAYS",
+      "dispersion": {
+        "limit": 1,
+        "shuffled": "true"
+      },
+      "domains": ["csd-target-3.thecdn.example.com"],
+      "coverageZoneOnly": "false",
+      "routingName": "cdn",
+      "matchsets": [
+        {
+          "protocol": "HTTP",
+          "matchlist": [{
+            "regex": ".*\\.csd-target-3\\..*",
+            "match-type": "HOST"
+          }]
+        }
+      ],
+      "regionalGeoBlocking": "false",
+      "ttls": {
+        "AAAA": "3600",
+        "SOA": "7200",
+        "A": "3600",
+        "NS": "3600"
+      },
+      "missLocation": {
+        "long": "-87.627778",
+        "lat": "41.881944"
+      },
+      "soa": {
+        "expire": "604800",
+        "minimum": "3 0",
+        "admin": "operations@thecdn.example.com",
+        "retry": "7200",
+        "refresh": "28800"
+      },
+      "geolocationProvider": "maxmindGeolocationService",
+      "ttl": "3600",
+      "ip6RoutingEnabled": "false"
+    },
+    "csd-target-4": {
+      "sslEnabled": "false",
+      "protocol": {
+        "acceptHttp" : "true",
+        "acceptHttps" : "false",
+        "redirectToHttps" : "false"
+      },
+      "requestHeaders": [
+        "User-Agent",
+        "MyHeader",
+        "Date"
+      ],
+      "dispersion": {
+        "limit": 1,
+        "shuffled": "true"
+      },
+      "domains": ["csd-target-4.thecdn.example.com"],
+      "coverageZoneOnly": "false",
+      "routingName": "cdn",
+      "matchsets": [
+        {
+          "protocol": "HTTP",
+          "matchlist": [{
+            "regex": ".*\\.csd-target-4\\..*",
+            "match-type": "HOST"
+          }]
+        }
+      ],
+      "regionalGeoBlocking": "false",
+      "ttls": {
+        "AAAA": "3600",
+        "SOA": "7200",
+        "A": "3600",
+        "NS": "3600"
+      },
+      "missLocation": {
+        "long": "-87.627778",
+        "lat": "41.881944"
+      },
+      "soa": {
+        "expire": "604800",
+        "minimum": "3 0",
+        "admin": "operations@thecdn.example.com",
+        "retry": "7200",
+        "refresh": "28800"
+      },
+      "geolocationProvider": "maxmindGeolocationService",
+      "ttl": "3600",
+      "ip6RoutingEnabled": "false"
+    },
+    "csd-target-5": {
+      "sslEnabled": "false",
+      "protocol": {
+        "acceptHttp" : "true",
+        "acceptHttps" : "false",
+        "redirectToHttps" : "false"
+      },
+      "requestHeaders": [
+        "User-Agent",
+        "MyHeader",
+        "Date"
+      ],
+      "dispersion": {
+        "limit": 1,
+        "shuffled": "true"
+      },
+      "domains": ["csd-target-5.thecdn.example.com"],
+      "coverageZoneOnly": "false",
+      "routingName": "cdn",
+      "matchsets": [
+        {
+          "protocol": "HTTP",
+          "matchlist": [{
+            "regex": ".*\\.csd-target-5\\..*",
+            "match-type": "HOST"
+          }]
+        }
+      ],
+      "regionalGeoBlocking": "false",
+      "ttls": {
+        "AAAA": "3600",
+        "SOA": "7200",
+        "A": "3600",
+        "NS": "3600"
+      },
+      "missLocation": {
+        "long": "-87.627778",
+        "lat": "41.881944"
+      },
+      "soa": {
+        "expire": "604800",
+        "minimum": "3 0",
+        "admin": "operations@thecdn.example.com",
+        "retry": "7200",
+        "refresh": "28800"
+      },
+      "geolocationProvider": "maxmindGeolocationService",
+      "ttl": "3600",
+      "ip6RoutingEnabled": "false"
+    },
     "client-steering-target-1": {
       "sslEnabled": "false",
       "protocol": {
@@ -1450,10 +1815,16 @@
       "localizationMethods": ["CZ", "DEEP_CZ", "GEO"],
       "longitude": -86.0,
       "latitude": 36.0
+    },
+    "location-csd": {
+      "localizationMethods": ["CZ", "DEEP_CZ", "GEO"],
+      "longitude": -87.0,
+      "latitude": 37.0
     }
   },
   "config": {
     "certificate.api.url": "http://${toHostname}/api/1.3/cdns/name/${cdnName}/sslkeys.json",
+    "client.steering.forced.diversity": "true",
     "federationmapping.polling.url": "http://${toHostname}/internal/api/1.3/federations.json",
     "federationmapping.polling.interval": "600000",
     "steeringmapping.polling.url": "http://${toHostname}/internal/api/1.3/steering.json",
@@ -1488,6 +1859,7 @@
       "refresh": "28800"
     },
     "coveragezone.polling.url": "http://localhost:8889/czf.json",
+    "deepcoveragezone.polling.url": "http://localhost:8889/dczmap.json",
     "api.auth.url": "http://${toHostname}/api/1.3/user/login",
     "certificates.polling.interval": "10000",
     "dnssec.enabled": "false"
diff --git a/traffic_router/core/src/test/resources/publish/CrStates b/traffic_router/core/src/test/resources/publish/CrStates
index 679c596..923bfb4 100644
--- a/traffic_router/core/src/test/resources/publish/CrStates
+++ b/traffic_router/core/src/test/resources/publish/CrStates
@@ -99,7 +99,10 @@
     "edge-cache-096": {"isAvailable": true},
     "edge-cache-097": {"isAvailable": true},
     "edge-cache-098": {"isAvailable": true},
-    "edge-cache-099": {"isAvailable": true}
+    "edge-cache-099": {"isAvailable": true},
+    "edge-cache-csd-1": {"isAvailable": true},
+    "edge-cache-csd-2": {"isAvailable": true},
+    "edge-cache-csd-3": {"isAvailable": true}
   },
   "deliveryServices": {
     "steering-test-1": {
@@ -146,6 +149,30 @@
       "disabledLocations": [],
       "isAvailable": true
     },
+    "client-steering-diversity-test": {
+      "disabledLocations": [],
+      "isAvailable": true
+    },
+    "csd-target-1": {
+      "disabledLocations": [],
+      "isAvailable": true
+    },
+    "csd-target-2": {
+      "disabledLocations": [],
+      "isAvailable": true
+    },
+    "csd-target-3": {
+      "disabledLocations": [],
+      "isAvailable": true
+    },
+    "csd-target-4": {
+      "disabledLocations": [],
+      "isAvailable": true
+    },
+    "csd-target-5": {
+      "disabledLocations": [],
+      "isAvailable": true
+    },
     "client-steering-test-1": {
       "disabledLocations": [],
       "isAvailable": true