You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ra...@apache.org on 2020/11/02 23:45:47 UTC

[trafficcontrol] branch 4.1.x updated: Check cache availability by IP version instead of filtering later (#5186) (#5238)

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

rawlin pushed a commit to branch 4.1.x
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/4.1.x by this push:
     new 36ea22a  Check cache availability by IP version instead of filtering later (#5186) (#5238)
36ea22a is described below

commit 36ea22a4d5413cde26018bdc14cfa23438ede017
Author: Rawlin Peters <ra...@apache.org>
AuthorDate: Mon Nov 2 16:45:34 2020 -0700

    Check cache availability by IP version instead of filtering later (#5186) (#5238)
    
    * Check cache availability by IP version instead of filtering later
    
    If the returned caches turn out to all be unavailable for the client's
    IP version, we don't want to 503. Instead, we want to follow the regular
    cache availability logic (falling back to closest available location,
    etc).
    
    * Add changelog
    
    * De-duplicate line in test
    
    (cherry picked from commit d0c9699e586d9c4fcf3d94407d9028aebc8766df)
---
 CHANGELOG.md                                       |   1 +
 .../api/controllers/CoverageZoneController.java    |   6 +-
 .../controllers/DeepCoverageZoneController.java    |   4 +-
 .../traffic_router/core/cache/Cache.java           |  74 +++---------
 .../traffic_router/core/dns/ZoneManager.java       |   3 +-
 .../traffic_router/core/router/TrafficRouter.java  | 133 ++++++++++-----------
 .../traffic_router/core/dns/ZoneManagerTest.java   |   3 +-
 .../traffic_router/core/external/SteeringTest.java |   4 +-
 .../traffic_router/core/loc/CoverageZoneTest.java  |  11 +-
 .../core/router/DNSRoutingMissesTest.java          |   7 +-
 .../core/router/TrafficRouterTest.java             |  60 +++++++++-
 11 files changed, 158 insertions(+), 148 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1870f20..8c39c5f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - Fixed #5074 - Traffic Monitor logging "CreateStats not adding availability data for server: not found in DeliveryServices" for MID caches
 - Fixed an issue that causes Traffic Router to mistakenly route to caches that had recently been set from ADMIN_DOWN to OFFLINE
 - Fixed a NullPointerException in Traffic Router that prevented it from properly updating cache health states
+- Fixed an issue where Traffic Router would erroneously return 503s or NXDOMAINs if the caches in a cachegroup were all unavailable for a client's requested IP version, rather than selecting caches from the next closest available cachegroup.
 - Traffic Ops Ort: Disabled ntpd verification (ntpd is deprecated in CentOS)
 - Fixed #5005: Traffic Monitor cannot be upgraded independently of Traffic Ops
 - Fixed an issue with Traffic Router failing to authenticate if secrets are changed
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/CoverageZoneController.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/CoverageZoneController.java
index b47743f..3642644 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/CoverageZoneController.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/CoverageZoneController.java
@@ -17,6 +17,7 @@ package com.comcast.cdn.traffic_control.traffic_router.api.controllers;
 
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache;
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheLocation;
+import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.router.TrafficRouterManager;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
@@ -38,7 +39,8 @@ public class CoverageZoneController {
 	public @ResponseBody
 	ResponseEntity<CacheLocation> getCacheLocationForIp(@RequestParam(name = "ip") final String ip,
 	                                    @RequestParam(name = "deliveryServiceId") final String deliveryServiceId) {
-		final CacheLocation cacheLocation = trafficRouterManager.getTrafficRouter().getCoverageZoneCacheLocation(ip, deliveryServiceId);
+		final IPVersions requestVersion = ip.contains(":") ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
+		final CacheLocation cacheLocation = trafficRouterManager.getTrafficRouter().getCoverageZoneCacheLocation(ip, deliveryServiceId, requestVersion);
 		if (cacheLocation == null) {
 			return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
 		}
@@ -50,7 +52,7 @@ public class CoverageZoneController {
 	public @ResponseBody
 	ResponseEntity<List<Cache>> getCachesForDeliveryService(@RequestParam(name = "deliveryServiceId") final String deliveryServiceId,
 	                                                        @RequestParam(name = "cacheLocationId") final String cacheLocationId) {
-		final List<Cache> caches = trafficRouterManager.getTrafficRouter().selectCachesByCZ(deliveryServiceId, cacheLocationId, null);
+		final List<Cache> caches = trafficRouterManager.getTrafficRouter().selectCachesByCZ(deliveryServiceId, cacheLocationId, null, IPVersions.ANY);
 		if (caches == null || caches.isEmpty()) {
 			return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
 		}
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/DeepCoverageZoneController.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/DeepCoverageZoneController.java
index 56d5ab0..fa58a36 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/DeepCoverageZoneController.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/DeepCoverageZoneController.java
@@ -16,6 +16,7 @@
 package com.comcast.cdn.traffic_control.traffic_router.api.controllers;
 
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheLocation;
+import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.router.TrafficRouterManager;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
@@ -35,7 +36,8 @@ public class DeepCoverageZoneController {
     public @ResponseBody
     ResponseEntity<CacheLocation> getCacheLocationForIp(@RequestParam(name = "ip") final String ip,
                                                         @RequestParam(name = "deliveryServiceId") final String deliveryServiceId) {
-        final CacheLocation cacheLocation = trafficRouterManager.getTrafficRouter().getCoverageZoneCacheLocation(ip, deliveryServiceId, true, null);
+        final IPVersions requestVersion = ip.contains(":") ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
+        final CacheLocation cacheLocation = trafficRouterManager.getTrafficRouter().getCoverageZoneCacheLocation(ip, deliveryServiceId, true, null, requestVersion);
         if (cacheLocation == null) {
             return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
         }
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 a0356e3..1247958 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
@@ -39,14 +39,18 @@ public class Cache implements Comparable<Cache>, Hashable<Cache> {
 	private static final Logger LOGGER = Logger.getLogger(Cache.class);
 	private static final int REPLICAS = 1000;
 
-	public enum IpVersions {
-		IPV4ONLY, IPV6ONLY, BOTH
+	public enum IPVersions {
+		IPV4ONLY, IPV6ONLY, ANY
 	}
-	private IpVersions ipAvailableVersions;
 	private final String id;
 	private String fqdn;
 	private List<InetRecord> ipAddresses;
-	private List<InetRecord> unavailableIpAddresses;
+	private InetAddress ip4;
+	private InetAddress ip6;
+	private boolean isAvailable = false;
+	private boolean ipv4Available = true;
+	private boolean ipv6Available = true;
+	private boolean hasAuthority = false;
 	private int port;
 	private final Map<String, DeliveryServiceReference> deliveryServices = new HashMap<String, DeliveryServiceReference>();
 	private final Geolocation geolocation;
@@ -191,8 +195,6 @@ public class Cache implements Comparable<Cache>, Hashable<Cache> {
 		}
 	}
 
-	boolean isAvailable = false;
-	boolean hasAuthority = false;
 	public void setIsAvailable(final boolean isAvailable) {
 		this.hasAuthority = true;
 		this.isAvailable = isAvailable;
@@ -203,11 +205,18 @@ public class Cache implements Comparable<Cache>, Hashable<Cache> {
 	public boolean isAvailable() {
 		return isAvailable;
 	}
-	InetAddress ip4;
-	InetAddress ip6;
+	public boolean isAvailable(final IPVersions requestVersion) {
+	    switch (requestVersion) {
+			case IPV4ONLY:
+			    return isAvailable && ipv4Available;
+			case IPV6ONLY:
+			    return isAvailable && ipv6Available;
+			default:
+			    return isAvailable;
+		}
+	}
 	public void setIpAddress(final String ip, final String ip6, final long ttl) throws UnknownHostException {
 		this.ipAddresses = new ArrayList<InetRecord>();
-		this.unavailableIpAddresses = new ArrayList<InetRecord>();
 
 		if (ip != null && !ip.isEmpty()) {
 			this.ip4 = InetAddress.getByName(ip);
@@ -233,8 +242,6 @@ public class Cache implements Comparable<Cache>, Hashable<Cache> {
 
 	@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
 	public void setState(final JsonNode state) {
-		final boolean ipv4Available;
-		final boolean ipv6Available;
 		if (state == null) {
 			LOGGER.warn("got null health state for " + fqdn + ". Setting it to unavailable!");
 			isAvailable = false;
@@ -245,44 +252,7 @@ public class Cache implements Comparable<Cache>, Hashable<Cache> {
 			ipv4Available = JsonUtils.optBoolean(state, "ipv4Available", true);
 			ipv6Available = JsonUtils.optBoolean(state, "ipv6Available", true);
 		}
-
-		final List<InetRecord> newlyAvailable = new ArrayList<>();
-		final List<InetRecord> newlyUnavailable = new ArrayList<>();
-
-		if (ipv4Available && !ipv6Available) {
-			this.ipAvailableVersions = IpVersions.IPV4ONLY;
-		} else if (ipv6Available && !ipv4Available) {
-			this.ipAvailableVersions = IpVersions.IPV6ONLY;
-		} else {
-			this.ipAvailableVersions = IpVersions.BOTH;
-		}
-
-		for (final InetRecord record : ipAddresses) {
-			if (record.getAddress().equals(ip4) && !ipv4Available) {
-				newlyUnavailable.add(record);
-			}
-			if (record.getAddress().equals(ip6) && !ipv6Available) {
-				newlyUnavailable.add(record);
-			}
-		}
-
-		for (final InetRecord record : unavailableIpAddresses) {
-			if (record.getAddress().equals(ip4) && ipv4Available) {
-				newlyAvailable.add(record);
-			}
-			if (record.getAddress().equals(ip6) && ipv6Available) {
-				newlyAvailable.add(record);
-			}
-		}
-
-		ipAddresses.addAll(newlyAvailable);
-		ipAddresses.removeAll(newlyUnavailable);
-		unavailableIpAddresses.addAll(newlyUnavailable);
-		unavailableIpAddresses.removeAll(newlyAvailable);
-
 		this.setIsAvailable(isAvailable);
-
-
 	}
 
 	@Override
@@ -323,12 +293,4 @@ public class Cache implements Comparable<Cache>, Hashable<Cache> {
 	public void setOrder(final int order) {
 		hashable.setOrder(order);
 	}
-
-	public IpVersions getIpAvailableVersions() {
-		return ipAvailableVersions;
-	}
-
-	public void setIpAvailableVersions(final IpVersions ipAvailableVersions) {
-		this.ipAvailableVersions = ipAvailableVersions;
-	}
 }
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManager.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManager.java
index 9c32bb5..1e56a5a 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManager.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManager.java
@@ -47,6 +47,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import com.comcast.cdn.traffic_control.traffic_router.core.router.TrafficRouterManager;
+import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.util.JsonUtils;
 import com.comcast.cdn.traffic_control.traffic_router.core.util.JsonUtilsException;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -563,7 +564,7 @@ public class ZoneManager extends Resolver {
 		request.setHostname(edgeName.toString(true)); // Name.toString(true) - omit the trailing dot
 
 		for (final CacheLocation cacheLocation : data.getCacheLocations()) {
-			final List<Cache> caches = tr.selectCachesByCZ(ds, cacheLocation);
+			final List<Cache> caches = tr.selectCachesByCZ(ds, cacheLocation, IPVersions.ANY);
 
 			if (caches == null) {
 				continue;
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 cbe9700..710fa71 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
@@ -47,9 +47,11 @@ import org.apache.log4j.Logger;
 import org.springframework.beans.BeansException;
 import org.springframework.context.ApplicationContext;
 import org.xbill.DNS.Name;
+import org.xbill.DNS.Type;
 import org.xbill.DNS.Zone;
 
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache;
+import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheLocation;
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheLocation.LocalizationMethod;
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheRegister;
@@ -150,7 +152,7 @@ public class TrafficRouter {
 	 *            the DeliveryService to check
 	 * @return collection of supported caches
 	 */
-	public List<Cache> getSupportingCaches(final List<Cache> caches, final DeliveryService ds) {
+	public List<Cache> getSupportingCaches(final List<Cache> caches, final DeliveryService ds, final IPVersions requestVersion) {
 		final List<Cache> supportingCaches = new ArrayList<Cache>();
 
 		for (final Cache cache : caches) {
@@ -158,7 +160,7 @@ public class TrafficRouter {
 				continue;
 			}
 
-			if (cache.hasAuthority() ? cache.isAvailable() : true) {
+			if (!cache.hasAuthority() || (cache.isAvailable(requestVersion))) {
 				supportingCaches.add(cache);
 			}
 		}
@@ -260,7 +262,7 @@ public class TrafficRouter {
 		return getLocation(clientIP, deliveryService.getGeolocationProvider(), deliveryService.getId());
 	}
 
-	public List<Cache> getCachesByGeo(final DeliveryService ds, final Geolocation clientLocation, final Track track) throws GeolocationException {
+	public List<Cache> getCachesByGeo(final DeliveryService ds, final Geolocation clientLocation, final Track track, final IPVersions requestVersion) throws GeolocationException {
 		int locationsTested = 0;
 
 		final int locationLimit = ds.getLocationLimit();
@@ -269,7 +271,7 @@ public class TrafficRouter {
 		final List<CacheLocation> cacheLocations = orderCacheLocations(cacheLocations1, clientLocation);
 
 		for (final CacheLocation location : cacheLocations) {
-			final List<Cache> caches = selectCaches(location, ds);
+			final List<Cache> caches = selectCaches(location, ds, requestVersion);
 			if (caches != null) {
 				track.setResultLocation(location.getGeolocation());
 				if (track.getResultLocation().equals(GEO_ZERO_ZERO)) {
@@ -295,23 +297,24 @@ public class TrafficRouter {
 		CacheLocation cacheLocation;
 		ResultType result = ResultType.CZ;
 		final boolean useDeep = enableDeep && (ds.getDeepCache() == DeliveryService.DeepCachingType.ALWAYS);
+		final IPVersions requestVersion = request.getClientIP().contains(":") ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
 
 		if (useDeep) {
 			// Deep caching is enabled. See if there are deep caches available
-			cacheLocation = getDeepCoverageZoneCacheLocation(request.getClientIP(), ds);
+			cacheLocation = getDeepCoverageZoneCacheLocation(request.getClientIP(), ds, requestVersion);
 			if (cacheLocation != null && cacheLocation.getCaches().size() != 0) {
 				// Found deep caches for this client, and there are caches that might be available there.
 				result = ResultType.DEEP_CZ;
 			} else {
 				// No deep caches for this client, would have used them if there were any. Fallback to regular CZ
-				cacheLocation = getCoverageZoneCacheLocation(request.getClientIP(), ds);
+				cacheLocation = getCoverageZoneCacheLocation(request.getClientIP(), ds, requestVersion);
 			}
 		} else {
 			// Deep caching not enabled for this Delivery Service; use the regular CZ
-			cacheLocation = getCoverageZoneCacheLocation(request.getClientIP(), ds, useDeep, track);
+			cacheLocation = getCoverageZoneCacheLocation(request.getClientIP(), ds, false, track, requestVersion);
 		}
 
-		List<Cache>caches = selectCachesByCZ(ds, cacheLocation, track, result);
+		List<Cache>caches = selectCachesByCZ(ds, cacheLocation, track, result, requestVersion);
 
 		if (caches != null) {
 			return caches;
@@ -320,20 +323,20 @@ public class TrafficRouter {
 		if (ds.isCoverageZoneOnly()) {
 			if (ds.getGeoRedirectUrl() != null) {
 				//use the NGB redirect
-				caches = enforceGeoRedirect(track, ds, request.getClientIP(), null);
+				caches = enforceGeoRedirect(track, ds, request.getClientIP(), null, requestVersion);
 			} else {
 				track.setResult(ResultType.MISS);
 				track.setResultDetails(ResultDetails.DS_CZ_ONLY);
 			}
 		} else if (track.continueGeo) {
 			// continue Geo can be disabled when backup group is used -- ended up an empty cache list if reach here
-			caches = selectCachesByGeo(request.getClientIP(), ds, cacheLocation, track);
+			caches = selectCachesByGeo(request.getClientIP(), ds, cacheLocation, track, requestVersion);
 		}
 
 		return caches;
 	}
 
-	public List<Cache> selectCachesByGeo(final String clientIp, final DeliveryService deliveryService, final CacheLocation cacheLocation, final Track track) throws GeolocationException {
+	public List<Cache> selectCachesByGeo(final String clientIp, final DeliveryService deliveryService, final CacheLocation cacheLocation, final Track track, final IPVersions requestVersion) throws GeolocationException {
 		Geolocation clientLocation = null;
 
 		try {
@@ -347,8 +350,8 @@ public class TrafficRouter {
 				//will use the NGB redirect
 				LOGGER.debug(String
 						.format("client is blocked by geolimit, use the NGB redirect url: %s",
-							deliveryService.getGeoRedirectUrl()));
-				return enforceGeoRedirect(track, deliveryService, clientIp, track.getClientGeolocation());
+								deliveryService.getGeoRedirectUrl()));
+				return enforceGeoRedirect(track, deliveryService, clientIp, track.getClientGeolocation(), requestVersion);
 			} else {
 				track.setResultDetails(ResultDetails.DS_CLIENT_GEO_UNSUPPORTED);
 				return null;
@@ -359,7 +362,7 @@ public class TrafficRouter {
 			clientLocation = defaultGeolocationsOverride.get(clientLocation.getCountryCode());
 		}
 
-		final List<Cache> caches = getCachesByGeo(deliveryService, clientLocation, track);
+		final List<Cache> caches = getCachesByGeo(deliveryService, clientLocation, track, requestVersion);
 
 		if (caches == null || caches.isEmpty()) {
 			track.setResultDetails(ResultDetails.GEO_NO_CACHE_FOUND);
@@ -395,8 +398,9 @@ public class TrafficRouter {
 			return result;
 		}
 
-		final CacheLocation cacheLocation = getCoverageZoneCacheLocation(request.getClientIP(), ds, false, track);
-		List<Cache> caches = selectCachesByCZ(ds, cacheLocation, track);
+		final IPVersions requestVersion = request.getQtype() == Type.AAAA ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
+		final CacheLocation cacheLocation = getCoverageZoneCacheLocation(request.getClientIP(), ds, false, track, requestVersion);
+		List<Cache> caches = selectCachesByCZ(ds, cacheLocation, track, requestVersion);
 
 		if (caches != null) {
 			track.setResult(ResultType.CZ);
@@ -425,7 +429,7 @@ public class TrafficRouter {
 		}
 
 		if (track.continueGeo) {
-			caches = selectCachesByGeo(request.getClientIP(), ds, cacheLocation, track);
+			caches = selectCachesByGeo(request.getClientIP(), ds, cacheLocation, track, requestVersion);
 		}
 
 		if (caches != null) {
@@ -497,24 +501,24 @@ public class TrafficRouter {
 		return ds.supportLocation(clientGeolocation);
 	}
 
-	public List<Cache> selectCachesByCZ(final DeliveryService ds, final CacheLocation cacheLocation) {
-		return selectCachesByCZ(ds, cacheLocation, null);
+	public List<Cache> selectCachesByCZ(final DeliveryService ds, final CacheLocation cacheLocation, final IPVersions requestVersion) {
+		return selectCachesByCZ(ds, cacheLocation, null, requestVersion);
 	}
 
-	public List<Cache> selectCachesByCZ(final String deliveryServiceId, final String cacheLocationId, final Track track) {
-		return selectCachesByCZ(cacheRegister.getDeliveryService(deliveryServiceId), cacheRegister.getCacheLocation(cacheLocationId), track);
+	public List<Cache> selectCachesByCZ(final String deliveryServiceId, final String cacheLocationId, final Track track, final IPVersions requestVersion) {
+		return selectCachesByCZ(cacheRegister.getDeliveryService(deliveryServiceId), cacheRegister.getCacheLocation(cacheLocationId), track, requestVersion);
 	}
 
-	private List<Cache> selectCachesByCZ(final DeliveryService ds, final CacheLocation cacheLocation, final Track track) {
-		return selectCachesByCZ(ds, cacheLocation, track, ResultType.CZ); // ResultType.CZ was the original default before DDC
+	private List<Cache> selectCachesByCZ(final DeliveryService ds, final CacheLocation cacheLocation, final Track track, final IPVersions requestVersion) {
+		return selectCachesByCZ(ds, cacheLocation, track, ResultType.CZ, requestVersion); // ResultType.CZ was the original default before DDC
 	}
 
-	private List<Cache> selectCachesByCZ(final DeliveryService ds, final CacheLocation cacheLocation, final Track track, final ResultType result) {
+	private List<Cache> selectCachesByCZ(final DeliveryService ds, final CacheLocation cacheLocation, final Track track, final ResultType result, final IPVersions requestVersion) {
 		if (cacheLocation == null || ds == null || !ds.isLocationAvailable(cacheLocation)) {
 			return null;
 		}
 
-		final List<Cache> caches = selectCaches(cacheLocation, ds);
+		final List<Cache> caches = selectCaches(cacheLocation, ds, requestVersion);
 
 		if (caches != null && track != null) {
 			track.setResult(result);
@@ -564,9 +568,6 @@ public class TrafficRouter {
 			final DeliveryService ds = steeringResult.getDeliveryService();
 			List<Cache> caches = selectCaches(request, ds, track);
 
-			if (caches != null) {
-				caches = editCacheListForIpVersion(!request.getClientIP().contains(":"), caches);
-			}
 			// child Delivery Services can use their query parameters
 			final String pathToHash = steeringHash + ds.extractSignificantQueryParams(request);
 
@@ -698,11 +699,7 @@ public class TrafficRouter {
 
 		routeResult.setDeliveryService(deliveryService);
 
-		List<Cache> caches = selectCaches(request, deliveryService, track);
-		if (caches != null) {
-			caches = editCacheListForIpVersion(!request.getClientIP().contains(":"), caches);
-		}
-
+		final List<Cache> caches = selectCaches(request, deliveryService, track);
 		if (caches == null || caches.isEmpty()) {
 			if (track.getResult() == ResultType.GEO_REDIRECT) {
 				routeResult.setUrl(new URL(deliveryService.getGeoRedirectUrl()));
@@ -740,18 +737,6 @@ public class TrafficRouter {
 		return routeResult;
 	}
 
-	private List<Cache> editCacheListForIpVersion(final boolean requestIsIpv4, final List<Cache> caches) {
-		final List<Cache> removeCaches = new ArrayList<>();
-		for (final Cache cache : caches) {
-			if ((!requestIsIpv4 && cache.getIpAvailableVersions() == Cache.IpVersions.IPV4ONLY) ||
-					requestIsIpv4 && cache.getIpAvailableVersions() == Cache.IpVersions.IPV6ONLY) {
-				removeCaches.add(cache);
-			}
-		}
-		caches.removeAll(removeCaches);
-		return caches;
-	}
-
 	@SuppressWarnings({"PMD.NPathComplexity"})
 	private List<SteeringResult> getSteeringResults(final HTTPRequest request, final Track track, final DeliveryService entryDeliveryService) {
 
@@ -836,12 +821,12 @@ public class TrafficRouter {
 		return null;
 	}
 
-	public CacheLocation getCoverageZoneCacheLocation(final String ip, final String deliveryServiceId) {
-		return getCoverageZoneCacheLocation(ip, deliveryServiceId, false, null); // default is not deep
+	public CacheLocation getCoverageZoneCacheLocation(final String ip, final String deliveryServiceId, final IPVersions requestVersion) {
+		return getCoverageZoneCacheLocation(ip, deliveryServiceId, false, null, requestVersion); // default is not deep
 	}
 
 	@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
-	public CacheLocation getCoverageZoneCacheLocation(final String ip, final String deliveryServiceId, final boolean useDeep, final Track track) {
+	public CacheLocation getCoverageZoneCacheLocation(final String ip, final String deliveryServiceId, final boolean useDeep, final Track track, final IPVersions requestVersion) {
 		final NetworkNode networkNode = useDeep ? getDeepNetworkNode(ip) : getNetworkNode(ip);
 		final LocalizationMethod localizationMethod = useDeep ? LocalizationMethod.DEEP_CZ : LocalizationMethod.CZ;
 
@@ -861,7 +846,7 @@ public class TrafficRouter {
 			return null;
 		}
 
-		if (cacheLocation != null && !getSupportingCaches(cacheLocation.getCaches(), deliveryService).isEmpty()) {
+		if (cacheLocation != null && !getSupportingCaches(cacheLocation.getCaches(), deliveryService, requestVersion).isEmpty()) {
 			return cacheLocation;
 		}
 
@@ -881,7 +866,7 @@ public class TrafficRouter {
 			return null;
 		}
 
-		if (cacheLocation != null && !getSupportingCaches(cacheLocation.getCaches(), deliveryService).isEmpty()) {
+		if (cacheLocation != null && !getSupportingCaches(cacheLocation.getCaches(), deliveryService, requestVersion).isEmpty()) {
 			// lazy loading in case a CacheLocation has not yet been associated with this NetworkNode
 			networkNode.setCacheLocation(cacheLocation);
 			return cacheLocation;
@@ -893,7 +878,7 @@ public class TrafficRouter {
 				if (bkCacheLocation != null && !bkCacheLocation.isEnabledFor(localizationMethod)) {
 					continue;
 				}
-				if (bkCacheLocation != null && !getSupportingCaches(bkCacheLocation.getCaches(), deliveryService).isEmpty()) {
+				if (bkCacheLocation != null && !getSupportingCaches(bkCacheLocation.getCaches(), deliveryService, requestVersion).isEmpty()) {
 					LOGGER.debug("Got backup CZ cache group " + bkCacheLocation.getId() + " for " + ip + ", ds " + deliveryServiceId);
 					if (track != null) {
 						track.setFromBackupCzGroup(true);
@@ -914,7 +899,8 @@ public class TrafficRouter {
 		// Check whether the CZF entry has a geolocation and use it if so.
 		List<CacheLocation> availableLocations = cacheRegister.filterAvailableLocations(deliveryServiceId);
 		availableLocations = filterEnabledLocations(availableLocations, localizationMethod);
-		final CacheLocation closestCacheLocation = getClosestCacheLocation(availableLocations, networkNode.getGeolocation(), cacheRegister.getDeliveryService(deliveryServiceId));
+		final CacheLocation closestCacheLocation = getClosestCacheLocation(availableLocations, networkNode.getGeolocation(), cacheRegister.getDeliveryService(deliveryServiceId), requestVersion);
+
 		if (closestCacheLocation != null) {
 			LOGGER.debug("Got closest CZ cache group " + closestCacheLocation.getId() + " for " + ip + ", ds " + deliveryServiceId);
 			if (track != null) {
@@ -930,16 +916,16 @@ public class TrafficRouter {
 				.collect(Collectors.toList());
 	}
 
-	public CacheLocation getDeepCoverageZoneCacheLocation(final String ip, final DeliveryService deliveryService) {
-		return getCoverageZoneCacheLocation(ip, deliveryService, true, null);
+	public CacheLocation getDeepCoverageZoneCacheLocation(final String ip, final DeliveryService deliveryService, final IPVersions requestVersion) {
+		return getCoverageZoneCacheLocation(ip, deliveryService, true, null, requestVersion);
 	}
 
-	protected CacheLocation getCoverageZoneCacheLocation(final String ip, final DeliveryService deliveryService, final boolean useDeep, final Track track) {
-		return getCoverageZoneCacheLocation(ip, deliveryService.getId(), useDeep, track);
+	public CacheLocation getCoverageZoneCacheLocation(final String ip, final DeliveryService deliveryService, final boolean useDeep, final Track track, final IPVersions requestVersion) {
+		return getCoverageZoneCacheLocation(ip, deliveryService.getId(), useDeep, track, requestVersion);
 	}
 
-	protected CacheLocation getCoverageZoneCacheLocation(final String ip, final DeliveryService deliveryService) {
-		return getCoverageZoneCacheLocation(ip, deliveryService.getId());
+	public CacheLocation getCoverageZoneCacheLocation(final String ip, final DeliveryService deliveryService, final IPVersions requestVersion) {
+		return getCoverageZoneCacheLocation(ip, deliveryService.getId(), requestVersion);
 	}
 
 	/**
@@ -977,8 +963,9 @@ public class TrafficRouter {
 			return null;
 		}
 
-		final CacheLocation coverageZoneCacheLocation = getCoverageZoneCacheLocation(ip, deliveryService, useDeep, null);
-		final List<Cache> caches = selectCachesByCZ(deliveryService, coverageZoneCacheLocation);
+		final IPVersions requestVersion = ip.contains(":") ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
+		final CacheLocation coverageZoneCacheLocation = getCoverageZoneCacheLocation(ip, deliveryService, useDeep, null, requestVersion);
+		final List<Cache> caches = selectCachesByCZ(deliveryService, coverageZoneCacheLocation, requestVersion);
 
 		if (caches == null || caches.isEmpty()) {
 			return null;
@@ -1010,15 +997,16 @@ public class TrafficRouter {
 			return null;
 		}
 
+		final IPVersions requestVersion = ip.contains(":") ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
 		List<Cache> caches = null;
 		if (deliveryService.isCoverageZoneOnly() && deliveryService.getGeoRedirectUrl() != null) {
-				//use the NGB redirect
-				caches = enforceGeoRedirect(StatTracker.getTrack(), deliveryService, ip, null);
+			//use the NGB redirect
+			caches = enforceGeoRedirect(StatTracker.getTrack(), deliveryService, ip, null, requestVersion);
 		} else {
-			final CacheLocation cacheLocation = getCoverageZoneCacheLocation(ip, deliveryServiceId);
+			final CacheLocation cacheLocation = getCoverageZoneCacheLocation(ip, deliveryServiceId, requestVersion);
 
 			try {
-				caches = selectCachesByGeo(ip, deliveryService, cacheLocation, StatTracker.getTrack());
+				caches = selectCachesByGeo(ip, deliveryService, cacheLocation, StatTracker.getTrack(), requestVersion);
 			} catch (GeolocationException e) {
 				LOGGER.warn("Failed gettting list of caches by geolocation for ip " + ip + " delivery service id '" + deliveryServiceId + "'");
 			}
@@ -1132,8 +1120,9 @@ public class TrafficRouter {
 			return null;
 		}
 
-		final CacheLocation coverageZoneCacheLocation = getCoverageZoneCacheLocation(ip, deliveryService, false, null);
-		final List<Cache> caches = selectCachesByCZ(deliveryService, coverageZoneCacheLocation);
+		final IPVersions requestVersion = ip.contains(":") ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
+		final CacheLocation coverageZoneCacheLocation = getCoverageZoneCacheLocation(ip, deliveryService, false, null, requestVersion);
+		final List<Cache> caches = selectCachesByCZ(deliveryService, coverageZoneCacheLocation, requestVersion);
 
 		if (caches == null || caches.isEmpty()) {
 			return null;
@@ -1216,7 +1205,7 @@ public class TrafficRouter {
 		return cacheLocations;
 	}
 
-	private CacheLocation getClosestCacheLocation(final List<CacheLocation> cacheLocations, final Geolocation clientLocation, final DeliveryService deliveryService) {
+	private CacheLocation getClosestCacheLocation(final List<CacheLocation> cacheLocations, final Geolocation clientLocation, final DeliveryService deliveryService, final IPVersions requestVersion) {
 		if (clientLocation == null) {
 			return null;
 		}
@@ -1224,7 +1213,7 @@ public class TrafficRouter {
 		final List<CacheLocation> orderedLocations = orderCacheLocations(cacheLocations, clientLocation);
 
 		for (final CacheLocation cacheLocation : orderedLocations) {
-			if (!getSupportingCaches(cacheLocation.getCaches(), deliveryService).isEmpty()) {
+			if (!getSupportingCaches(cacheLocation.getCaches(), deliveryService, requestVersion).isEmpty()) {
 				return cacheLocation;
 			}
 		}
@@ -1241,12 +1230,12 @@ public class TrafficRouter {
 	 *            the delivery service for the request
 	 * @return the selected cache or null if none can be found
 	 */
-	private List<Cache> selectCaches(final CacheLocation location, final DeliveryService ds) {
+	private List<Cache> selectCaches(final CacheLocation location, final DeliveryService ds, final IPVersions requestVersion) {
 		if (LOGGER.isDebugEnabled()) {
 			LOGGER.debug("Trying location: " + location.getId());
 		}
 
-		final List<Cache> caches = getSupportingCaches(location.getCaches(), ds);
+		final List<Cache> caches = getSupportingCaches(location.getCaches(), ds, requestVersion);
 		if (caches.isEmpty()) {
 			if (LOGGER.isDebugEnabled()) {
 				LOGGER.debug("No online, supporting caches were found at location: "
@@ -1282,7 +1271,7 @@ public class TrafficRouter {
 		return dnssecZoneDiffingEnabled;
 	}
 
-	private List<Cache> enforceGeoRedirect(final Track track, final DeliveryService ds, final String clientIp, final Geolocation queriedClientLocation) {
+	private List<Cache> enforceGeoRedirect(final Track track, final DeliveryService ds, final String clientIp, final Geolocation queriedClientLocation, final IPVersions requestVersion) {
 		final String urlType = ds.getGeoRedirectUrlType();
 		track.setResult(ResultType.GEO_REDIRECT);
 
@@ -1324,7 +1313,7 @@ public class TrafficRouter {
 		List<Cache> caches = null;
 
 		try {
-			caches = getCachesByGeo(ds, clientLocation, track);
+			caches = getCachesByGeo(ds, clientLocation, track, requestVersion);
 		} catch (GeolocationException e) {
 			LOGGER.error("Failed getting caches by geolocation " + e.getMessage());
 		}
diff --git a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManagerTest.java b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManagerTest.java
index 4438b1c..3eec71d 100644
--- a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManagerTest.java
+++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManagerTest.java
@@ -51,6 +51,7 @@ import com.comcast.cdn.traffic_control.traffic_router.core.TestBase;
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache;
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheLocation;
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheRegister;
+import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.DeliveryService;
 import com.comcast.cdn.traffic_control.traffic_router.core.router.TrafficRouter;
 import com.comcast.cdn.traffic_control.traffic_router.core.router.TrafficRouterManager;
@@ -108,7 +109,7 @@ public class ZoneManagerTest {
 			final Name edgeName = new Name(ds.getRoutingName() + "." + domain + ".");
 
 			for (CacheLocation location : cacheRegister.getCacheLocations()) {
-				final List<Cache> caches = trafficRouter.selectCachesByCZ(ds, location);
+				final List<Cache> caches = trafficRouter.selectCachesByCZ(ds, location, IPVersions.IPV4ONLY);
 
 				if (caches == null) {
 					continue;
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 a72f18d..e365ef8 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
@@ -326,8 +326,8 @@ public class SteeringTest {
 		HttpPost httpPost = new HttpPost("http://localhost:" + testHttpPort + "/steering");
 		httpClient.execute(httpPost).close();
 
-		// steering is checked every 15 seconds by default.
-		Thread.sleep(30 * 1000);
+		// a polling interval of 60 seconds is common
+		Thread.sleep(90 * 1000);
 
 		Map<String, List<String>> rehashedPaths = new HashMap<>();
 		rehashedPaths.put(smallerTarget, new ArrayList<String>());
diff --git a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/CoverageZoneTest.java b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/CoverageZoneTest.java
index 90b5979..d45827c 100644
--- a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/CoverageZoneTest.java
+++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/CoverageZoneTest.java
@@ -19,6 +19,7 @@ import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache;
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheLocation;
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheLocation.LocalizationMethod;
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheRegister;
+import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.DeliveryService;
 import com.comcast.cdn.traffic_control.traffic_router.core.router.TrafficRouter;
 import com.comcast.cdn.traffic_control.traffic_router.geolocation.Geolocation;
@@ -100,19 +101,19 @@ public class CoverageZoneTest {
 
 		trafficRouter = PowerMockito.mock(TrafficRouter.class);
 		Whitebox.setInternalState(trafficRouter, "cacheRegister", cacheRegister);
-		when(trafficRouter.getCoverageZoneCacheLocation("12.23.34.45", "delivery-service-1")).thenCallRealMethod();
-		when(trafficRouter.getCoverageZoneCacheLocation("12.23.34.45", "delivery-service-1", false, null)).thenCallRealMethod();
+		when(trafficRouter.getCoverageZoneCacheLocation("12.23.34.45", "delivery-service-1", IPVersions.IPV4ONLY)).thenCallRealMethod();
+		when(trafficRouter.getCoverageZoneCacheLocation("12.23.34.45", "delivery-service-1", false, null, IPVersions.IPV4ONLY)).thenCallRealMethod();
 		when(trafficRouter.getCacheRegister()).thenReturn(cacheRegister);
 		when(trafficRouter.orderCacheLocations(anyListOf(CacheLocation.class),any(Geolocation.class))).thenCallRealMethod();
-		when(trafficRouter.getSupportingCaches(anyListOf(Cache.class), eq(deliveryService))).thenCallRealMethod();
+		when(trafficRouter.getSupportingCaches(anyListOf(Cache.class), eq(deliveryService), any(IPVersions.class))).thenCallRealMethod();
 		when(trafficRouter.filterEnabledLocations(anyListOf(CacheLocation.class), any(CacheLocation.LocalizationMethod.class))).thenCallRealMethod();
 		PowerMockito.when(trafficRouter, "getNetworkNode", "12.23.34.45").thenReturn(eastNetworkNode);
-		PowerMockito.when(trafficRouter, "getClosestCacheLocation", anyListOf(CacheLocation.class), any(CacheLocation.class), any(DeliveryService.class)).thenCallRealMethod();
+		PowerMockito.when(trafficRouter, "getClosestCacheLocation", anyListOf(CacheLocation.class), any(CacheLocation.class), any(DeliveryService.class), any(IPVersions.class)).thenCallRealMethod();
 	}
 
 	@Test
 	public void trafficRouterReturnsNearestCacheGroupForDeliveryService() throws Exception {
-		CacheLocation cacheLocation = trafficRouter.getCoverageZoneCacheLocation("12.23.34.45", "delivery-service-1");
+		CacheLocation cacheLocation = trafficRouter.getCoverageZoneCacheLocation("12.23.34.45", "delivery-service-1", IPVersions.IPV4ONLY);
 		assertThat(cacheLocation.getId(), equalTo("west-cache-group"));
 		// NOTE: far-east-cache-group is actually closer to the client but isn't enabled for CZ-localization and must be filtered out
 	}
diff --git a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/DNSRoutingMissesTest.java b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/DNSRoutingMissesTest.java
index bb80022..8a9d37b 100644
--- a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/DNSRoutingMissesTest.java
+++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/DNSRoutingMissesTest.java
@@ -17,6 +17,7 @@ package com.comcast.cdn.traffic_control.traffic_router.core.router;
 
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheLocation;
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheRegister;
+import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.DeliveryService;
 import com.comcast.cdn.traffic_control.traffic_router.core.loc.FederationRegistry;
 import com.comcast.cdn.traffic_control.traffic_router.core.request.DNSRequest;
@@ -63,7 +64,7 @@ public class DNSRoutingMissesTest {
         trafficRouter = mock(TrafficRouter.class);
         when(trafficRouter.getCacheRegister()).thenReturn(mock(CacheRegister.class));
         Whitebox.setInternalState(trafficRouter, "federationRegistry", federationRegistry);
-        when(trafficRouter.selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class))).thenCallRealMethod();
+        when(trafficRouter.selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class), any(IPVersions.class))).thenCallRealMethod();
 
         track = spy(StatTracker.getTrack());
         doCallRealMethod().when(trafficRouter).route(request, track);
@@ -153,7 +154,7 @@ public class DNSRoutingMissesTest {
 
     @Test
     public void itSetsDetailsWhenCacheNotFoundByGeolocation() throws Exception {
-        doCallRealMethod().when(trafficRouter).selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class));
+        doCallRealMethod().when(trafficRouter).selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class), any(IPVersions.class));
         CacheLocation cacheLocation = mock(CacheLocation.class);
         CacheRegister cacheRegister = mock(CacheRegister.class);
 
@@ -164,7 +165,7 @@ public class DNSRoutingMissesTest {
         when(deliveryService.getRoutingName()).thenReturn("edge");
 
         doReturn(deliveryService).when(trafficRouter).selectDeliveryService(request, false);
-        doReturn(cacheLocation).when(trafficRouter).getCoverageZoneCacheLocation("192.168.34.56", deliveryService);
+        doReturn(cacheLocation).when(trafficRouter).getCoverageZoneCacheLocation("192.168.34.56", deliveryService, IPVersions.IPV4ONLY);
         doReturn(cacheRegister).when(trafficRouter).getCacheRegister();
 
         trafficRouter.route(request, track);
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 ad8abd2..34624a6 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
@@ -21,6 +21,7 @@ import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheLocation.L
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.CacheRegister;
 import com.comcast.cdn.traffic_control.traffic_router.core.cache.InetRecord;
 import com.comcast.cdn.traffic_control.traffic_router.core.config.CertificateChecker;
+import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.DeliveryService;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.Dispersion;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.SteeringRegistry;
@@ -49,6 +50,7 @@ import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -140,9 +142,9 @@ public class TrafficRouterTest {
         List<Cache> caches = new ArrayList<Cache>();
         caches.add(cache);
         when(trafficRouter.selectCaches(any(HTTPRequest.class), any(DeliveryService.class), any(Track.class))).thenReturn(caches);
-        when(trafficRouter.selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class))).thenCallRealMethod();
+        when(trafficRouter.selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class), any(IPVersions.class))).thenCallRealMethod();
         when(trafficRouter.getClientLocation(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class))).thenReturn(new Geolocation(40, -100));
-        when(trafficRouter.getCachesByGeo(any(DeliveryService.class), any(Geolocation.class), any(Track.class))).thenCallRealMethod();
+        when(trafficRouter.getCachesByGeo(any(DeliveryService.class), any(Geolocation.class), any(Track.class), any(IPVersions.class))).thenCallRealMethod();
         when(trafficRouter.getCacheRegister()).thenReturn(cacheRegister);
         when(trafficRouter.orderCacheLocations(any(List.class), any(Geolocation.class))).thenCallRealMethod();
 
@@ -152,6 +154,54 @@ public class TrafficRouterTest {
     }
 
     @Test
+    public void itFiltersByIPAvailability() throws Exception {
+
+        DeliveryService ds = mock(DeliveryService.class);
+
+        Cache cacheIPv4 = mock(Cache.class);
+        when(cacheIPv4.hasDeliveryService(anyString())).thenReturn(true);
+        when(cacheIPv4.hasAuthority()).thenReturn(true);
+        when(cacheIPv4.isAvailable(any(IPVersions.class))).thenCallRealMethod();
+        doCallRealMethod().when(cacheIPv4).setIsAvailable(anyBoolean());
+        setInternalState(cacheIPv4, "ipv4Available", true);
+        setInternalState(cacheIPv4, "ipv6Available", false);
+        cacheIPv4.setIsAvailable(true);
+        when(cacheIPv4.getId()).thenReturn("cache IPv4");
+
+        Cache cacheIPv6 = mock(Cache.class);
+        when(cacheIPv6.hasDeliveryService(anyString())).thenReturn(true);
+        when(cacheIPv6.hasAuthority()).thenReturn(true);
+        when(cacheIPv6.isAvailable(any(IPVersions.class))).thenCallRealMethod();
+        doCallRealMethod().when(cacheIPv6).setIsAvailable(anyBoolean());
+        setInternalState(cacheIPv6, "ipv4Available", false);
+        setInternalState(cacheIPv6, "ipv6Available", true);
+        cacheIPv6.setIsAvailable(true);
+        when(cacheIPv6.getId()).thenReturn("cache IPv6");
+
+        List<Cache> caches = new ArrayList<Cache>();
+        caches.add(cacheIPv4);
+        caches.add(cacheIPv6);
+
+        when(trafficRouter.getSupportingCaches(any(List.class), any(DeliveryService.class), any(IPVersions.class))).thenCallRealMethod();
+
+        List<Cache> supportingIPv4Caches = trafficRouter.getSupportingCaches(caches, ds, IPVersions.IPV4ONLY);
+        assertThat(supportingIPv4Caches.size(), equalTo(1));
+        assertThat(supportingIPv4Caches.get(0).getId(), equalTo("cache IPv4"));
+
+        List<Cache> supportingIPv6Caches = trafficRouter.getSupportingCaches(caches, ds, IPVersions.IPV6ONLY);
+        assertThat(supportingIPv6Caches.size(), equalTo(1));
+        assertThat(supportingIPv6Caches.get(0).getId(), equalTo("cache IPv6"));
+
+        List<Cache> supportingEitherCaches = trafficRouter.getSupportingCaches(caches, ds, IPVersions.ANY);
+        assertThat(supportingEitherCaches.size(), equalTo(2));
+
+        cacheIPv6.setIsAvailable(false);
+        List<Cache> supportingAvailableCaches = trafficRouter.getSupportingCaches(caches, ds, IPVersions.ANY);
+        assertThat(supportingAvailableCaches.size(), equalTo(1));
+        assertThat(supportingAvailableCaches.get(0).getId(), equalTo("cache IPv4"));
+    }
+
+    @Test
     public void itSetsResultToGeo() throws Exception {
         Cache cache = mock(Cache.class);
         when(cache.hasDeliveryService(anyString())).thenReturn(true);
@@ -171,15 +221,15 @@ public class TrafficRouterTest {
 
         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();
+        when(trafficRouter.selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class), any(IPVersions.class))).thenCallRealMethod();
 
         Geolocation clientLocation = new Geolocation(40, -100);
         when(trafficRouter.getClientLocation(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class))).thenReturn(clientLocation);
 
-        when(trafficRouter.getCachesByGeo(any(DeliveryService.class), any(Geolocation.class), any(Track.class))).thenCallRealMethod();
+        when(trafficRouter.getCachesByGeo(any(DeliveryService.class), any(Geolocation.class), any(Track.class), any(IPVersions.class))).thenCallRealMethod();
         when(trafficRouter.filterEnabledLocations(any(List.class), any(LocalizationMethod.class))).thenCallRealMethod();
         when(trafficRouter.orderCacheLocations(any(List.class), any(Geolocation.class))).thenCallRealMethod();
-        when(trafficRouter.getSupportingCaches(any(List.class), any(DeliveryService.class))).thenCallRealMethod();
+        when(trafficRouter.getSupportingCaches(any(List.class), any(DeliveryService.class), any(IPVersions.class))).thenCallRealMethod();
 
         HTTPRequest httpRequest = new HTTPRequest();
         httpRequest.setClientIP("192.168.10.11");