Repository: cilium/cilium
Pull request #10529 (2020-03-10 13:49:54)

pkg/bpf: remove unused (Get|Set)MapPrefix funcs
Also unexport `SetMapRoot` since it's not used outside the package.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/cilium/cilium/10529)
<!-- Reviewable:end -->


tklauser: test-me-please
coveralls: 
[![Coverage Status](https://coveralls.io/builds/29250348/badge)](https://coveralls.io/builds/29250348)

Coverage increased (+0.02%) to 45.622% when pulling **6efdd27aec2073fa446a80eb55a835d9e7e2158e on pr/tklauser/bpf-remove-unused** into **0dc12fb94ed19adcef1c92f13fa50c42bc01546c on master**.


Patch:
diff --git a/.travis.yml b/.travis.yml
index 7121b1bea7..a88cc235fb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,10 +12,6 @@ arch:
   - amd64
   - arm64
 
-jobs:
-  allow_failures:
-  - arch: arm64
-
 if: branch = master OR type = pull_request
 
 addons:
diff --git a/Documentation/community.rst b/Documentation/community.rst
index d51c8b498e..36dbd94e7f 100644
--- a/Documentation/community.rst
+++ b/Documentation/community.rst
@@ -17,10 +17,10 @@ and meeting links.
 ====================== ===================================== ============= ================================================================================
 SIG                    Meeting                               Slack         Description
 ====================== ===================================== ============= ================================================================================
-Datapath               Every 2 weeks                         #sig-datapath Owner of all BPF and Linux kernel related datapath code.
+Datapath               Wednesdays, 08:00 PT                  #sig-datapath Owner of all BPF and Linux kernel related datapath code.
 Documentation          None                                  #sig-docs     All documentation related discussions
-Envoy                  Every 2 weeks                         #sig-envoy    Envoy, Istio and maintenance of all L7 protocol parsers.
-Policy                 Every Wed, 9:30 PT (`Policy-Zoom`_)   #sig-policy   All topics related to policy. The SIG is responsible for all security relevant APIs and the enforcement logic.
+Envoy                  Biweekly on Thursdays, 09:00 PT       #sig-envoy    Envoy, Istio and maintenance of all L7 protocol parsers.
+Policy                 None                                  #sig-policy   All topics related to policy. The SIG is responsible for all security relevant APIs and the enforcement logic.
 Release Management     None                                  #launchpad    Responsible for the release management and backport process.
 ====================== ===================================== ============= ================================================================================
 
diff --git a/Documentation/policy/language.rst b/Documentation/policy/language.rst
index 758c08c941..8831872960 100644
--- a/Documentation/policy/language.rst
+++ b/Documentation/policy/language.rst
@@ -300,12 +300,13 @@ remote-node
     nodes. (Requires the option ``enable-remote-node-identity`` to be enabled)
 cluster
     Cluster is the logical group of all network endpoints inside of the local
-    cluster. This includes all Cilium-managed endpoints of the local cluster.
-    It also includes the host entity to cover host networking containers as
-    well as the init entity to include endpoints currently being bootstrapped.
+    cluster. This includes all Cilium-managed endpoints of the local cluster,
+    unmanaged endpoints in the local cluster, as well as the host,
+    remote-node, and init identities.
 init
     The init entity contains all endpoints in bootstrap phase for which the
-    security identity has not been resolved yet. See section
+    security identity has not been resolved yet. This is typically only
+    observed in non-Kubernetes environments. See section
     :ref:`endpoint_lifecycle` for details.
 world
     The world entity corresponds to all endpoints outside of the cluster.
diff --git a/Makefile b/Makefile
index ecf7b982b6..b6b18093a6 100644
--- a/Makefile
+++ b/Makefile
@@ -35,6 +35,7 @@ JOB_BASE_NAME ?= cilium_test
 UTC_DATE=$(shell date -u "+%Y-%m-%d")
 
 GO_VERSION := $(shell cat GO_VERSION)
+GOARCH := $(shell $(GO) env GOARCH)
 
 # Since there's a bug with NFS or the kernel, the flock syscall hangs the documentation
 # build in the developer VM. For this reason the documentation build is skipped if NFS
@@ -100,6 +101,7 @@ ifeq ($(SKIP_KVSTORES),"false")
 	@echo Starting key-value store containers...
 	-$(CONTAINER_ENGINE_FULL) rm -f "cilium-etcd-test-container" 2> /dev/null
 	$(CONTAINER_ENGINE_FULL) run -d \
+		-e ETCD_UNSUPPORTED_ARCH=$(GOARCH) \
 		--name "cilium-etcd-test-container" \
 		-p 4002:4001 \
 		$(ETCD_IMAGE) \
@@ -369,14 +371,14 @@ microk8s: check-microk8s
 	@echo "  DEPLOY image to microk8s ($(LOCAL_IMAGE))"
 	$(CONTAINER_ENGINE_FULL) tag cilium/cilium-dev:$(LOCAL_IMAGE_TAG) $(LOCAL_IMAGE)
 	$(CONTAINER_ENGINE_FULL) push $(LOCAL_IMAGE)
-	$(QUIET)kubectl apply -f contrib/k8s/microk8s-prepull.yaml
-	$(QUIET)kubectl -n kube-system delete pod -l name=prepull
-	$(QUIET)kubectl -n kube-system rollout status ds/prepull
+	$(QUIET)microk8s.kubectl apply -f contrib/k8s/microk8s-prepull.yaml
+	$(QUIET)microk8s.kubectl -n kube-system delete pod -l name=prepull
+	$(QUIET)microk8s.kubectl -n kube-system rollout status ds/prepull
 	@echo
 	@echo "Update image tag like this when ready:"
-	@echo "    kubectl -n kube-system set image ds/cilium cilium-agent=$(LOCAL_IMAGE)"
+	@echo "    microk8s.kubectl -n kube-system set image ds/cilium cilium-agent=$(LOCAL_IMAGE)"
 	@echo "Or, redeploy the Cilium pods:"
-	@echo "    kubectl -n kube-system delete pod -l k8s-app=cilium"
+	@echo "    microk8s.kubectl -n kube-system delete pod -l k8s-app=cilium"
 
 precheck: ineffassign logging-subsys-field
 	@$(ECHO_CHECK) contrib/scripts/check-fmt.sh
diff --git a/USERS.md b/USERS.md
index 4bd0aea915..76c14ce1af 100644
--- a/USERS.md
+++ b/USERS.md
@@ -102,3 +102,8 @@ Users (Alphabetically)
       U: ENI Networking, Service load-balancing, Direct routing (via Bird)
       L: https://ctripcloud.github.io/cilium/network/2020/01/19/trip-first-step-towards-cloud-native-networking.html
       Q: @ArthurChiao
+
+    * N: Wildlife Studios
+      D: Wildlife Studios is using Cilium in AWS for all their game production clusters (self hosted k8s)
+      U: ClusterMesh, Global Service Load Balancing.
+      Q: @Oki @luanguimaraesla
diff --git a/bpf/include/bpf/compiler.h b/bpf/include/bpf/compiler.h
index eb2e608713..4c7af22b9a 100644
--- a/bpf/include/bpf/compiler.h
+++ b/bpf/include/bpf/compiler.h
@@ -43,7 +43,7 @@
 #endif
 
 #ifndef __fetch
-# define __fetch(X)		(__u32)(&(X))
+# define __fetch(X)		(__u32)(__u64)(&(X))
 #endif
 
 #ifndef build_bug_on
diff --git a/bpf/lib/nat.h b/bpf/lib/nat.h
index e51e9f97f4..6803c49cc1 100644
--- a/bpf/lib/nat.h
+++ b/bpf/lib/nat.h
@@ -529,7 +529,7 @@ static __always_inline int snat_v4_process(struct __ctx_buff *ctx, int dir,
 		}
 		break;
 	default:
-		return DROP_NAT_UNSUPP_PROTO;
+		return NAT_PUNT_TO_STACK;
 	};
 
 	if (snat_v4_can_skip(target, &tuple, dir))
@@ -996,7 +996,7 @@ static __always_inline int snat_v6_process(struct __ctx_buff *ctx, int dir,
 		}
 		break;
 	default:
-		return DROP_NAT_UNSUPP_PROTO;
+		return NAT_PUNT_TO_STACK;
 	};
 
 	if (snat_v6_can_skip(target, &tuple, dir))
diff --git a/install/kubernetes/cilium/charts/config/templates/configmap.yaml b/install/kubernetes/cilium/charts/config/templates/configmap.yaml
index c436d3bd35..6689e67217 100644
--- a/install/kubernetes/cilium/charts/config/templates/configmap.yaml
+++ b/install/kubernetes/cilium/charts/config/templates/configmap.yaml
@@ -65,6 +65,7 @@ data:
   # NOTE that this will open the port on the nodes where Cilium operator pod
   # is scheduled.
   operator-prometheus-serve-addr: ":{{ .Values.global.operatorPrometheus.port }}"
+  enable-metrics: "true"
 {{- end }}
 
   # Enable IPv4 addressing. If enabled, all endpoints are allocated an IPv4
diff --git a/pkg/bpf/bpffs_linux.go b/pkg/bpf/bpffs_linux.go
index 8d0a75693e..086dab5b4a 100644
--- a/pkg/bpf/bpffs_linux.go
+++ b/pkg/bpf/bpffs_linux.go
@@ -47,9 +47,9 @@ func lockDown() {
 	lockedDown = true
 }
 
-func SetMapRoot(path string) {
+func setMapRoot(path string) {
 	if lockedDown {
-		panic("SetMapRoot() call after MapRoot was read")
+		panic("setMapRoot() call after MapRoot was read")
 	}
 	mapRoot = path
 }
@@ -59,18 +59,6 @@ func GetMapRoot() string {
 	return mapRoot
 }
 
-func SetMapPrefix(path string) {
-	if lockedDown {
-		panic("SetMapPrefix() call after MapPrefix was read")
-	}
-	mapPrefix = path
-}
-
-func GetMapPrefix() string {
-	once.Do(lockDown)
-	return mapPrefix
-}
-
 func MapPrefixPath() string {
 	once.Do(lockDown)
 	return filepath.Join(mapRoot, mapPrefix)
@@ -182,7 +170,7 @@ func hasMultipleMounts() (bool, error) {
 // checkOrMountCustomLocation tries to check or mount the BPF filesystem in the
 // given path.
 func checkOrMountCustomLocation(bpfRoot string, printWarning bool) error {
-	SetMapRoot(bpfRoot)
+	setMapRoot(bpfRoot)
 
 	// Check whether the custom location has a BPFFS mount.
 	mounted, bpffsInstance, err := mountinfo.IsMountFS(mountinfo.FilesystemTypeBPFFS, bpfRoot)
@@ -192,7 +180,7 @@ func checkOrMountCustomLocation(bpfRoot string, printWarning bool) error {
 
 	// If the custom location has no mount, let's mount BPFFS there.
 	if !mounted {
-		SetMapRoot(bpfRoot)
+		setMapRoot(bpfRoot)
 		if err := mountFS(printWarning); err != nil {
 			return err
 		}
@@ -256,7 +244,7 @@ func checkOrMountDefaultLocations(printWarning bool) error {
 			"for more information, see: https://cilium.link/err-bpf-mount",
 			defaults.DefaultMapRootFallback,
 		)
-		SetMapRoot(defaults.DefaultMapRootFallback)
+		setMapRoot(defaults.DefaultMapRootFallback)
 
 		cMounted, cBpffsInstance, err := mountinfo.IsMountFS(mountinfo.FilesystemTypeBPFFS, mapRoot)
 		if err != nil {
diff --git a/pkg/fqdn/name_manager.go b/pkg/fqdn/name_manager.go
index f8d78cd706..cac7201964 100644
--- a/pkg/fqdn/name_manager.go
+++ b/pkg/fqdn/name_manager.go
@@ -87,20 +87,29 @@ func (n *NameManager) GetModel() *models.NameManager {
 	}
 }
 
-// RegisterForIdentityUpdates exposes this FQDNSelector so that identities
+// Lock must be held during any calls to RegisterForIdentityUpdatesLocked or
+// UnregisterForIdentityUpdatesLocked.
+func (n *NameManager) Lock() {
+	n.Mutex.Lock()
+}
+
+// Unlock must be called after calls to RegisterForIdentityUpdatesLocked or
+// UnregisterForIdentityUpdatesLocked are done.
+func (n *NameManager) Unlock() {
+	n.Mutex.Unlock()
+}
+
+// RegisterForIdentityUpdatesLocked exposes this FQDNSelector so that identities
 // for IPs contained in a DNS response that matches said selector can be
 // propagated back to the SelectorCache via `UpdateFQDNSelector`. All DNS names
 // contained within the NameManager's cache are iterated over to see if they match
 // the FQDNSelector. All IPs which correspond to the DNS names which match this
 // Selector will be returned as CIDR identities, as other DNS Names which have
 // already been resolved may match this FQDNSelector.
-func (n *NameManager) RegisterForIdentityUpdates(selector api.FQDNSelector) []identity.NumericIdentity {
-
-	n.Mutex.Lock()
+func (n *NameManager) RegisterForIdentityUpdatesLocked(selector api.FQDNSelector) []identity.NumericIdentity {
 	_, exists := n.allSelectors[selector]
 	if exists {
-		log.WithField("fqdnSelector", selector).Error("FQDNSelector was already registered for updates, returning without any identities")
-		n.Mutex.Unlock()
+		log.WithField("fqdnSelector", selector).Warning("FQDNSelector was already registered for updates, returning without any identities")
 		return nil
 	}
 
@@ -109,7 +118,6 @@ func (n *NameManager) RegisterForIdentityUpdates(selector api.FQDNSelector) []id
 	regex, err := selector.ToRegex()
 	if err != nil {
 		log.WithError(err).WithField("fqdnSelector", selector).Error("FQDNSelector did not compile to valid regex")
-		n.Mutex.Unlock()
 		return nil
 	}
 
@@ -120,7 +128,6 @@ func (n *NameManager) RegisterForIdentityUpdates(selector api.FQDNSelector) []id
 
 	n.allSelectors[selector] = regex
 	_, selectorIPMapping := mapSelectorsToIPs(map[api.FQDNSelector]struct{}{selector: {}}, n.cache)
-	n.Mutex.Unlock()
 
 	// Allocate identities for each IPNet and then map to selector
 	selectorIPs := selectorIPMapping[selector]
@@ -142,16 +149,14 @@ func (n *NameManager) RegisterForIdentityUpdates(selector api.FQDNSelector) []id
 	return numIDs
 }
 
-// UnregisterForIdentityUpdates removes this FQDNSelector from the set of
+// UnregisterForIdentityUpdatesLocked removes this FQDNSelector from the set of
 // FQDNSelectors which are being tracked by the NameManager. No more updates for IPs
 // which correspond to said selector are propagated.
-func (n *NameManager) UnregisterForIdentityUpdates(selector api.FQDNSelector) {
-	n.Mutex.Lock()
+func (n *NameManager) UnregisterForIdentityUpdatesLocked(selector api.FQDNSelector) {
 	delete(n.allSelectors, selector)
 	if len(selector.MatchName) > 0 {
 		delete(n.namesToPoll, prepareMatchName(selector.MatchName))
 	}
-	n.Mutex.Unlock()
 }
 
 // NewNameManager creates an initialized NameManager.
@@ -198,8 +203,11 @@ func (n *NameManager) GetDNSNames() (dnsNames []string) {
 // have changed for a name, store which rules must be updated in rulesToUpdate,
 // regenerate them, and emit via UpdateSelectors.
 func (n *NameManager) UpdateGenerateDNS(ctx context.Context, lookupTime time.Time, updatedDNSIPs map[string]*DNSIPRecords) (wg *sync.WaitGroup, err error) {
+	n.Mutex.Lock()
+	defer n.Mutex.Unlock()
+
 	// Update IPs in n
-	fqdnSelectorsToUpdate, updatedDNSNames := n.UpdateDNSIPs(lookupTime, updatedDNSIPs)
+	fqdnSelectorsToUpdate, updatedDNSNames := n.updateDNSIPs(lookupTime, updatedDNSIPs)
 	for dnsName, IPs := range updatedDNSNames {
 		log.WithFields(logrus.Fields{
 			"matchName":             dnsName,
@@ -208,7 +216,7 @@ func (n *NameManager) UpdateGenerateDNS(ctx context.Context, lookupTime time.Tim
 		}).Debug("Updated FQDN with new IPs")
 	}
 
-	namesMissingIPs, selectorIPMapping := n.GenerateSelectorUpdates(fqdnSelectorsToUpdate)
+	namesMissingIPs, selectorIPMapping := n.generateSelectorUpdates(fqdnSelectorsToUpdate)
 	if len(namesMissingIPs) != 0 {
 		log.WithField(logfields.DNSName, namesMissingIPs).
 			Debug("No IPs to insert when generating DNS name selected by ToFQDN rule")
@@ -222,6 +230,8 @@ func (n *NameManager) UpdateGenerateDNS(ctx context.Context, lookupTime time.Tim
 // matchNames that match them will cause these rules to regenerate.
 func (n *NameManager) ForceGenerateDNS(ctx context.Context, namesToRegen []string) (wg *sync.WaitGroup, err error) {
 	n.Mutex.Lock()
+	defer n.Mutex.Unlock()
+
 	affectedFQDNSels := make(map[api.FQDNSelector]struct{}, 0)
 	for _, dnsName := range namesToRegen {
 		for fqdnSel, fqdnRegEx := range n.allSelectors {
@@ -236,7 +246,6 @@ func (n *NameManager) ForceGenerateDNS(ctx context.Context, namesToRegen []strin
 		log.WithField(logfields.DNSName, namesMissingIPs).
 			Debug("No IPs to insert when generating DNS name selected by ToFQDN rule")
 	}
-	n.Mutex.Unlock()
 
 	// emit the new rules
 	return n.config.
@@ -249,18 +258,15 @@ func (n *NameManager) CompleteBootstrap() {
 	n.Unlock()
 }
 
-// UpdateDNSIPs updates the IPs for each DNS name in updatedDNSIPs.
+// updateDNSIPs updates the IPs for each DNS name in updatedDNSIPs.
 // It returns:
 // affectedSelectors: a set of all FQDNSelectors which match DNS Names whose
 // corresponding set of IPs has changed.
 // updatedNames: a map of DNS names to all the valid IPs we store for each.
-func (n *NameManager) UpdateDNSIPs(lookupTime time.Time, updatedDNSIPs map[string]*DNSIPRecords) (affectedSelectors map[api.FQDNSelector]struct{}, updatedNames map[string][]net.IP) {
+func (n *NameManager) updateDNSIPs(lookupTime time.Time, updatedDNSIPs map[string]*DNSIPRecords) (affectedSelectors map[api.FQDNSelector]struct{}, updatedNames map[string][]net.IP) {
 	updatedNames = make(map[string][]net.IP, len(updatedDNSIPs))
 	affectedSelectors = make(map[api.FQDNSelector]struct{}, len(updatedDNSIPs))
 
-	n.Lock()
-	defer n.Unlock()
-
 perDNSName:
 	for dnsName, lookupIPs := range updatedDNSIPs {
 		updated := n.updateIPsForName(lookupTime, dnsName, lookupIPs.IPs, lookupIPs.TTL)
@@ -295,14 +301,11 @@ perDNSName:
 	return affectedSelectors, updatedNames
 }
 
-// GenerateSelectorUpdates iterates over all names in the DNS cache managed by
+// generateSelectorUpdates iterates over all names in the DNS cache managed by
 // gen and figures out to which FQDNSelectors managed by the cache these names
 // map. Returns the set of FQDNSelectors which map to no IPs, and a mapping
 // of FQDNSelectors to IPs.
-func (n *NameManager) GenerateSelectorUpdates(fqdnSelectors map[api.FQDNSelector]struct{}) (namesMissingIPs []api.FQDNSelector, selectorIPMapping map[api.FQDNSelector][]net.IP) {
-	n.Lock()
-	defer n.Unlock()
-
+func (n *NameManager) generateSelectorUpdates(fqdnSelectors map[api.FQDNSelector]struct{}) (namesMissingIPs []api.FQDNSelector, selectorIPMapping map[api.FQDNSelector][]net.IP) {
 	return mapSelectorsToIPs(fqdnSelectors, n.cache)
 }
 
diff --git a/pkg/ipcache/cidr.go b/pkg/ipcache/cidr.go
index 995b2497c1..feea372140 100644
--- a/pkg/ipcache/cidr.go
+++ b/pkg/ipcache/cidr.go
@@ -71,6 +71,9 @@ func allocateCIDRs(prefixes []*net.IPNet) ([]*identity.Identity, error) {
 		allocateCtx, cancel := context.WithTimeout(context.Background(), option.Config.IPAllocationTimeout)
 		defer cancel()
 
+		if IdentityAllocator == nil {
+			return nil, fmt.Errorf("IdentityAllocator not initialized!")
+		}
 		id, isNew, err := IdentityAllocator.AllocateIdentity(allocateCtx, cidr.GetCIDRLabels(prefix), false)
 		if err != nil {
 			IdentityAllocator.ReleaseSlice(context.Background(), nil, usedIdentities)
diff --git a/pkg/policy/l4.go b/pkg/policy/l4.go
index 660c6eb6e7..e3f223a42b 100644
--- a/pkg/policy/l4.go
+++ b/pkg/policy/l4.go
@@ -333,7 +333,8 @@ func (l4 *L4Filter) ToMapState(direction trafficdirection.TrafficDirection) MapS
 }
 
 // IdentitySelectionUpdated implements CachedSelectionUser interface
-// This call is made while holding selector cache lock, must beware of deadlocking!
+// This call is made while holding name manager and selector cache
+// locks, must beware of deadlocking!
 //
 // The caller is responsible for making sure the same identity is not
 // present in both 'added' and 'deleted'.
diff --git a/pkg/policy/selectorcache.go b/pkg/policy/selectorcache.go
index 2d01ed3eae..bc13256f7b 100644
--- a/pkg/policy/selectorcache.go
+++ b/pkg/policy/selectorcache.go
@@ -102,7 +102,7 @@ func (s CachedSelectorSlice) SelectsAllEndpoints() bool {
 // the CachedSelectors pushed by it.
 type CachedSelectionUser interface {
 	// IdentitySelectionUpdated implementations MUST NOT call back
-	// to selector cache while executing this function!
+	// to the name manager or the selector cache while executing this function!
 	//
 	// The caller is responsible for making sure the same identity is not
 	// present in both 'added' and 'deleted'.
@@ -147,8 +147,13 @@ type CachedSelectionUser interface {
 type identitySelector interface {
 	CachedSelector
 	addUser(CachedSelectionUser) (added bool)
-	removeUser(CachedSelectionUser) (last bool)
+
+	// Called with NameManager and SelectorCache locks held
+	removeUser(CachedSelectionUser, identityNotifier) (last bool)
+
+	// This may be called while the NameManager lock is held
 	notifyUsers(added, deleted []identity.NumericIdentity)
+
 	numUsers() int
 }
 
@@ -313,18 +318,19 @@ func (s *selectorManager) addUser(user CachedSelectionUser) (added bool) {
 }
 
 // lock must be held
-func (s *selectorManager) removeUser(user CachedSelectionUser) (last bool) {
+func (s *selectorManager) removeUser(user CachedSelectionUser, dnsProxy identityNotifier) (last bool) {
 	delete(s.users, user)
 	return len(s.users) == 0
 }
 
-func (f *fqdnSelector) removeUser(user CachedSelectionUser) (last bool) {
+// locks must be held for the dnsProxy and the SelectorCache
+func (f *fqdnSelector) removeUser(user CachedSelectionUser, dnsProxy identityNotifier) (last bool) {
 	delete(f.users, user)
-	removed := len(f.users) == 0
-	if removed {
-		f.dnsProxy.UnregisterForIdentityUpdates(f.selector)
+	if len(f.users) == 0 {
+		dnsProxy.UnregisterForIdentityUpdatesLocked(f.selector)
+		return true
 	}
-	return removed
+	return false
 }
 
 // lock must be held
@@ -363,8 +369,6 @@ func (s *selectorManager) setSelections(selections *[]identity.NumericIdentity)
 type fqdnSelector struct {
 	selectorManager
 	selector api.FQDNSelector
-	// dnsProxy updates the set of identities which correspond to this selector.
-	dnsProxy identityNotifier
 }
 
 // lock must be held
@@ -386,7 +390,15 @@ func (f *fqdnSelector) notifyUsers(added, deleted []identity.NumericIdentity) {
 // relationship is contained only via DNS responses, which are handled
 // externally.
 type identityNotifier interface {
-	// RegisterForIdentityUpdates exposes this FQDNSelector so that identities
+	// Lock must be held during any calls to RegisterForIdentityUpdatesLocked or
+	// UnregisterForIdentityUpdatesLocked.
+	Lock()
+
+	// Unlock must be called after calls to RegisterForIdentityUpdatesLocked or
+	// UnregisterForIdentityUpdatesLocked are done.
+	Unlock()
+
+	// RegisterForIdentityUpdatesLocked exposes this FQDNSelector so that identities
 	// for IPs contained in a DNS response that matches said selector can be
 	// propagated back to the SelectorCache via `UpdateFQDNSelector`. When called,
 	// implementers (currently `pkg/fqdn/RuleGen`) should iterate over all DNS
@@ -401,15 +413,15 @@ type identityNotifier interface {
 	// set of CIDR identities which match this FQDNSelector already from the
 	// identityNotifier on the first pass; any subsequent updates will eventually
 	// call `UpdateFQDNSelector`.
-	RegisterForIdentityUpdates(selector api.FQDNSelector) (identities []identity.NumericIdentity)
+	RegisterForIdentityUpdatesLocked(selector api.FQDNSelector) (identities []identity.NumericIdentity)
 
-	// UnregisterForIdentityUpdates removes this FQDNSelector from the set of
+	// UnregisterForIdentityUpdatesLocked removes this FQDNSelector from the set of
 	// FQDNSelectors which are being tracked by the identityNotifier. The result
 	// of this is that no more updates for IPs which correspond to said selector
 	// are propagated back to the SelectorCache via `UpdateFQDNSelector`.
 	// This occurs when there are no more users of a given FQDNSelector for the
 	// SelectorCache.
-	UnregisterForIdentityUpdates(selector api.FQDNSelector)
+	UnregisterForIdentityUpdatesLocked(selector api.FQDNSelector)
 }
 
 type labelIdentitySelector struct {
@@ -485,7 +497,6 @@ func (sc *SelectorCache) updateFQDNSelector(fqdnSelec api.FQDNSelector, identiti
 				cachedSelections: make(map[identity.NumericIdentity]struct{}),
 			},
 			selector: fqdnSelec,
-			dnsProxy: sc.localIdentityNotifier,
 		}
 		sc.selectors[fqdnKey] = fqdnSel
 	} else {
@@ -565,6 +576,10 @@ func (sc *SelectorCache) updateFQDNSelector(fqdnSelec api.FQDNSelector, identiti
 func (sc *SelectorCache) AddFQDNSelector(user CachedSelectionUser, fqdnSelec api.FQDNSelector) (cachedSelector CachedSelector, added bool) {
 	key := fqdnSelec.String()
 
+	// Lock NameManager before the SelectorCache
+	sc.localIdentityNotifier.Lock()
+	defer sc.localIdentityNotifier.Unlock()
+
 	// If the selector already exists, use it.
 	sc.mutex.Lock()
 	fqdnSel, exists := sc.selectors[key]
@@ -585,7 +600,6 @@ func (sc *SelectorCache) AddFQDNSelector(user CachedSelectionUser, fqdnSelec api
 			cachedSelections: make(map[identity.NumericIdentity]struct{}),
 		},
 		selector: fqdnSelec,
-		dnsProxy: sc.localIdentityNotifier,
 	}
 
 	// Make the FQDN subsystem aware of this selector and fetch identities
@@ -599,7 +613,7 @@ func (sc *SelectorCache) AddFQDNSelector(user CachedSelectionUser, fqdnSelec api
 	// If this is called twice, one of the results will arbitrarily contain
 	// a real slice of ids, while the other will receive nil. We must fold
 	// them together below.
-	ids := newFQDNSel.dnsProxy.RegisterForIdentityUpdates(newFQDNSel.selector)
+	ids := sc.localIdentityNotifier.RegisterForIdentityUpdatesLocked(newFQDNSel.selector)
 
 	// Do not go through the identity cache to see what identities "match" this
 	// selector. This has to be updated via whatever is getting the CIDR identities
@@ -705,11 +719,12 @@ func (sc *SelectorCache) AddIdentitySelector(user CachedSelectionUser, selector
 	return newIDSel, true
 }
 
+// lock must be held
 func (sc *SelectorCache) removeSelectorLocked(selector CachedSelector, user CachedSelectionUser) {
 	key := selector.String()
 	sel, exists := sc.selectors[key]
 	if exists {
-		if sel.removeUser(user) {
+		if sel.removeUser(user, sc.localIdentityNotifier) {
 			delete(sc.selectors, key)
 		}
 	}
@@ -717,18 +732,22 @@ func (sc *SelectorCache) removeSelectorLocked(selector CachedSelector, user Cach
 
 // RemoveSelector removes CachedSelector for the user.
 func (sc *SelectorCache) RemoveSelector(selector CachedSelector, user CachedSelectionUser) {
+	sc.localIdentityNotifier.Lock()
 	sc.mutex.Lock()
 	sc.removeSelectorLocked(selector, user)
 	sc.mutex.Unlock()
+	sc.localIdentityNotifier.Unlock()
 }
 
 // RemoveSelectors removes CachedSelectorSlice for the user.
 func (sc *SelectorCache) RemoveSelectors(selectors CachedSelectorSlice, user CachedSelectionUser) {
+	sc.localIdentityNotifier.Lock()
 	sc.mutex.Lock()
 	for _, selector := range selectors {
 		sc.removeSelectorLocked(selector, user)
 	}
 	sc.mutex.Unlock()
+	sc.localIdentityNotifier.Unlock()
 }
 
 // ChangeUser changes the CachedSelectionUser that gets updates on the
@@ -741,7 +760,8 @@ func (sc *SelectorCache) ChangeUser(selector CachedSelector, from, to CachedSele
 		// Add before remove so that the count does not dip to zero in between,
 		// as this causes FQDN unregistration (if applicable).
 		idSel.addUser(to)
-		idSel.removeUser(from)
+		// ignoring the return value as we have just added a user above
+		idSel.removeUser(from, sc.localIdentityNotifier)
 	}
 	sc.mutex.Unlock()
 }


Test patch:
diff --git a/pkg/fqdn/dnspoller_test.go b/pkg/fqdn/dnspoller_test.go
index 2ba5f02ca3..fbd83f597b 100644
--- a/pkg/fqdn/dnspoller_test.go
+++ b/pkg/fqdn/dnspoller_test.go
@@ -152,9 +152,11 @@ func (ds *FQDNTestSuite) TestNameManagerSelectorHandling(c *C) {
 			poller      = NewDNSPoller(cfg, nameManager)
 		)
 
+		nameManager.Lock()
 		for _, fqdnSel := range testCase.selectorsToAdd {
-			nameManager.RegisterForIdentityUpdates(fqdnSel)
+			nameManager.RegisterForIdentityUpdatesLocked(fqdnSel)
 		}
+		nameManager.Unlock()
 		for i := testCase.lookupIterationsAfterAdd; i > 0; i-- {
 			err := poller.LookupUpdateDNS(context.Background())
 			c.Assert(err, IsNil, Commentf("Error running DNS lookups"))
@@ -162,9 +164,11 @@ func (ds *FQDNTestSuite) TestNameManagerSelectorHandling(c *C) {
 
 		// delete rules listed in the test case (note: we don't delete any unless
 		// they are listed)
+		nameManager.Lock()
 		for _, fqdnSel := range testCase.selectorsToDelete {
-			nameManager.UnregisterForIdentityUpdates(fqdnSel)
+			nameManager.UnregisterForIdentityUpdatesLocked(fqdnSel)
 		}
+		nameManager.Unlock()
 		for i := testCase.lookupIterationsAfterDelete; i > 0; i-- {
 			err := poller.LookupUpdateDNS(context.Background())
 			c.Assert(err, IsNil, Commentf("Error running DNS lookups"))
diff --git a/pkg/fqdn/name_manager_test.go b/pkg/fqdn/name_manager_test.go
index 777009505e..d45ac4525b 100644
--- a/pkg/fqdn/name_manager_test.go
+++ b/pkg/fqdn/name_manager_test.go
@@ -61,7 +61,9 @@ func (ds *FQDNTestSuite) TestNameManagerCIDRGeneration(c *C) {
 	)
 
 	// add rules
-	ids := nameManager.RegisterForIdentityUpdates(ciliumIOSel)
+	nameManager.Lock()
+	ids := nameManager.RegisterForIdentityUpdatesLocked(ciliumIOSel)
+	nameManager.Unlock()
 	c.Assert(len(ids), Equals, 0)
 	c.Assert(ids, Not(IsNil))
 
@@ -112,10 +114,12 @@ func (ds *FQDNTestSuite) TestNameManagerMultiIPUpdate(c *C) {
 
 	// add rules
 	selectorsToAdd := api.FQDNSelectorSlice{ciliumIOSel, githubSel}
+	nameManager.Lock()
 	for _, sel := range selectorsToAdd {
-		ids := nameManager.RegisterForIdentityUpdates(sel)
+		ids := nameManager.RegisterForIdentityUpdatesLocked(sel)
 		c.Assert(ids, Not(IsNil))
 	}
+	nameManager.Unlock()
 
 	// poll DNS once, check that we only generate 1 IP for cilium.io
 	selIPMap = make(map[api.FQDNSelector][]net.IP)
@@ -150,16 +154,18 @@ func (ds *FQDNTestSuite) TestNameManagerMultiIPUpdate(c *C) {
 	c.Assert(selIPMap[githubSel][0].Equal(net.ParseIP("3.3.3.3")), Equals, true, Commentf("Incorrect IP mapping to FQDN"))
 	c.Assert(selIPMap[githubSel][1].Equal(net.ParseIP("4.4.4.4")), Equals, true, Commentf("Incorrect IP mapping to FQDN"))
 
-	// Second registration returns nil
-	ids := nameManager.RegisterForIdentityUpdates(githubSel)
+	// Second registration fails because IdenitityAllocator is not initialized
+	nameManager.Lock()
+	ids := nameManager.RegisterForIdentityUpdatesLocked(githubSel)
 	c.Assert(ids, IsNil)
 
-	nameManager.UnregisterForIdentityUpdates(githubSel)
+	nameManager.UnregisterForIdentityUpdatesLocked(githubSel)
 	_, exists := nameManager.allSelectors[githubSel]
 	c.Assert(exists, Equals, false)
 
-	nameManager.UnregisterForIdentityUpdates(ciliumIOSel)
+	nameManager.UnregisterForIdentityUpdatesLocked(ciliumIOSel)
 	_, exists = nameManager.allSelectors[ciliumIOSel]
 	c.Assert(exists, Equals, false)
+	nameManager.Unlock()
 
 }
diff --git a/pkg/k8s/network_policy_test.go b/pkg/k8s/network_policy_test.go
index 918d196dbd..3ebf3f3186 100644
--- a/pkg/k8s/network_policy_test.go
+++ b/pkg/k8s/network_policy_test.go
@@ -29,6 +29,7 @@ import (
 	"github.com/cilium/cilium/pkg/labels"
 	"github.com/cilium/cilium/pkg/policy"
 	"github.com/cilium/cilium/pkg/policy/api"
+	"github.com/cilium/cilium/pkg/testutils"
 
 	. "gopkg.in/check.v1"
 	v1 "k8s.io/api/core/v1"
@@ -100,6 +101,12 @@ var (
 
 type DummySelectorCacheUser struct{}
 
+func testNewPolicyRepository() *policy.Repository {
+	repo := policy.NewPolicyRepository(nil, nil)
+	repo.GetSelectorCache().SetLocalIdentityNotifier(testutils.NewDummyIdentityNotifier())
+	return repo
+}
+
 func (d *DummySelectorCacheUser) IdentitySelectionUpdated(selector policy.CachedSelector, selections, added, deleted []identity.NumericIdentity) {
 }
 
@@ -160,7 +167,7 @@ func (s *K8sSuite) TestParseNetworkPolicyIngress(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(len(rules), Equals, 1)
 
-	repo := policy.NewPolicyRepository(nil, nil)
+	repo := testNewPolicyRepository()
 
 	repo.AddList(rules)
 	c.Assert(repo.AllowsIngressRLocked(&ctx), Equals, api.Denied)
@@ -288,7 +295,7 @@ func (s *K8sSuite) TestParseNetworkPolicyMultipleSelectors(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(len(rules), Equals, 1)
 
-	repo := policy.NewPolicyRepository(nil, nil)
+	repo := testNewPolicyRepository()
 	repo.AddList(rules)
 
 	endpointLabels := labels.LabelArray{
@@ -486,7 +493,7 @@ func (s *K8sSuite) TestParseNetworkPolicyEgress(c *C) {
 		Trace: policy.TRACE_VERBOSE,
 	}
 
-	repo := policy.NewPolicyRepository(nil, nil)
+	repo := testNewPolicyRepository()
 	repo.AddList(rules)
 	// Because search context did not contain port-specific policy, deny is
 	// expected.
@@ -540,7 +547,7 @@ func (s *K8sSuite) TestParseNetworkPolicyEgress(c *C) {
 }
 
 func parseAndAddRules(c *C, p *networkingv1.NetworkPolicy) *policy.Repository {
-	repo := policy.NewPolicyRepository(nil, nil)
+	repo := testNewPolicyRepository()
 	rules, err := ParseNetworkPolicy(p)
 	c.Assert(err, IsNil)
 	rev := repo.GetRevision()
@@ -698,7 +705,7 @@ func (s *K8sSuite) TestParseNetworkPolicyEmptyFrom(c *C) {
 		Trace: policy.TRACE_VERBOSE,
 	}
 
-	repo := policy.NewPolicyRepository(nil, nil)
+	repo := testNewPolicyRepository()
 	repo.AddList(rules)
 	c.Assert(repo.AllowsIngressRLocked(&ctx), Equals, api.Allowed)
 
@@ -722,7 +729,7 @@ func (s *K8sSuite) TestParseNetworkPolicyEmptyFrom(c *C) {
 	rules, err = ParseNetworkPolicy(netPolicy2)
 	c.Assert(err, IsNil)
 	c.Assert(len(rules), Equals, 1)
-	repo = policy.NewPolicyRepository(nil, nil)
+	repo = testNewPolicyRepository()
 	repo.AddList(rules)
 	c.Assert(repo.AllowsIngressRLocked(&ctx), Equals, api.Allowed)
 }
@@ -753,7 +760,7 @@ func (s *K8sSuite) TestParseNetworkPolicyDenyAll(c *C) {
 		Trace: policy.TRACE_VERBOSE,
 	}
 
-	repo := policy.NewPolicyRepository(nil, nil)
+	repo := testNewPolicyRepository()
 	repo.AddList(rules)
 	c.Assert(repo.AllowsIngressRLocked(&ctx), Equals, api.Denied)
 }
@@ -864,7 +871,7 @@ func (s *K8sSuite) TestNetworkPolicyExamples(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(len(rules), Equals, 1)
 
-	repo := policy.NewPolicyRepository(nil, nil)
+	repo := testNewPolicyRepository()
 	repo.AddList(rules)
 	ctx := policy.SearchContext{
 		From: labels.LabelArray{
@@ -998,7 +1005,7 @@ func (s *K8sSuite) TestNetworkPolicyExamples(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(len(rules), Equals, 1)
 
-	repo = policy.NewPolicyRepository(nil, nil)
+	repo = testNewPolicyRepository()
 	repo.AddList(rules)
 	ctx = policy.SearchContext{
 		From: labels.LabelArray{
@@ -1066,7 +1073,7 @@ func (s *K8sSuite) TestNetworkPolicyExamples(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(len(rules), Equals, 1)
 
-	repo = policy.NewPolicyRepository(nil, nil)
+	repo = testNewPolicyRepository()
 	repo.AddList(rules)
 	ctx = policy.SearchContext{
 		From: labels.LabelArray{
@@ -1205,7 +1212,7 @@ func (s *K8sSuite) TestNetworkPolicyExamples(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(len(rules), Equals, 1)
 
-	repo = policy.NewPolicyRepository(nil, nil)
+	repo = testNewPolicyRepository()
 	// add example 4
 	repo.AddList(rules)
 
diff --git a/pkg/policy/distillery_test.go b/pkg/policy/distillery_test.go
index beeadad1aa..d388a6dcc9 100644
--- a/pkg/policy/distillery_test.go
+++ b/pkg/policy/distillery_test.go
@@ -329,7 +329,7 @@ func Test_MergeL3(t *testing.T) {
 		identity.NumericIdentity(identityFoo): labelsFoo,
 		identity.NumericIdentity(identityBar): labelsBar,
 	}
-	selectorCache := NewSelectorCache(identityCache)
+	selectorCache := testNewSelectorCache(identityCache)
 
 	tests := []struct {
 		test   int
@@ -368,7 +368,7 @@ func Test_MergeRules(t *testing.T) {
 	identityCache := cache.IdentityCache{
 		identity.NumericIdentity(identityFoo): labelsFoo,
 	}
-	selectorCache := NewSelectorCache(identityCache)
+	selectorCache := testNewSelectorCache(identityCache)
 
 	tests := []struct {
 		test   int
@@ -441,7 +441,7 @@ func Test_AllowAll(t *testing.T) {
 		identity.NumericIdentity(identityFoo): labelsFoo,
 		identity.NumericIdentity(identityBar): labelsBar,
 	}
-	selectorCache := NewSelectorCache(identityCache)
+	selectorCache := testNewSelectorCache(identityCache)
 
 	tests := []struct {
 		test     int
diff --git a/pkg/policy/l4_filter_test.go b/pkg/policy/l4_filter_test.go
index 51a6061c50..909d31b6c4 100644
--- a/pkg/policy/l4_filter_test.go
+++ b/pkg/policy/l4_filter_test.go
@@ -38,7 +38,7 @@ var (
 
 	dummySelectorCacheUser = &DummySelectorCacheUser{}
 	c                      = cache.NewCachingIdentityAllocator(&allocator.IdentityAllocatorOwnerMock{})
-	testSelectorCache      = NewSelectorCache(c.GetIdentityCache())
+	testSelectorCache      = testNewSelectorCache(c.GetIdentityCache())
 
 	wildcardCachedSelector, _ = testSelectorCache.AddIdentitySelector(dummySelectorCacheUser, api.WildcardEndpointSelector)
 
diff --git a/pkg/policy/selectorcache_test.go b/pkg/policy/selectorcache_test.go
index 8556af476c..7ed6c40a29 100644
--- a/pkg/policy/selectorcache_test.go
+++ b/pkg/policy/selectorcache_test.go
@@ -23,6 +23,7 @@ import (
 	k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
 	"github.com/cilium/cilium/pkg/labels"
 	"github.com/cilium/cilium/pkg/policy/api"
+	"github.com/cilium/cilium/pkg/testutils"
 
 	. "gopkg.in/check.v1"
 )
@@ -129,7 +130,8 @@ func (ds *SelectorCacheTestSuite) TearDownTest(c *C) {
 }
 
 func (ds *SelectorCacheTestSuite) TestAddRemoveSelector(c *C) {
-	sc := NewSelectorCache(cache.IdentityCache{})
+	sc := testNewSelectorCache(cache.IdentityCache{})
+
 	// Add some identities to the identity cache
 	sc.UpdateIdentities(cache.IdentityCache{
 		1234: labels.Labels{"app": labels.NewLabel("app", "test", labels.LabelSourceK8s),
@@ -178,7 +180,8 @@ func (ds *SelectorCacheTestSuite) TestAddRemoveSelector(c *C) {
 }
 
 func (ds *SelectorCacheTestSuite) TestMultipleIdentitySelectors(c *C) {
-	sc := NewSelectorCache(cache.IdentityCache{})
+	sc := testNewSelectorCache(cache.IdentityCache{})
+
 	// Add some identities to the identity cache
 	sc.UpdateIdentities(cache.IdentityCache{
 		1234: labels.Labels{"app": labels.NewLabel("app", "test", labels.LabelSourceK8s)}.LabelArray(),
@@ -213,7 +216,8 @@ func (ds *SelectorCacheTestSuite) TestMultipleIdentitySelectors(c *C) {
 }
 
 func (ds *SelectorCacheTestSuite) TestIdentityUpdates(c *C) {
-	sc := NewSelectorCache(cache.IdentityCache{})
+	sc := testNewSelectorCache(cache.IdentityCache{})
+
 	// Add some identities to the identity cache
 	sc.UpdateIdentities(cache.IdentityCache{
 		1234: labels.Labels{"app": labels.NewLabel("app", "test", labels.LabelSourceK8s)}.LabelArray(),
@@ -273,8 +277,8 @@ func (ds *SelectorCacheTestSuite) TestIdentityUpdates(c *C) {
 }
 
 func (ds *SelectorCacheTestSuite) TestFQDNSelectorUpdates(c *C) {
-	sc := NewSelectorCache(cache.IdentityCache{})
-	sc.SetLocalIdentityNotifier(newDummyIdentityNotifier())
+	sc := testNewSelectorCache(cache.IdentityCache{})
+
 	// Add some identities to the identity cache
 	googleSel := api.FQDNSelector{MatchName: "google.com"}
 	ciliumSel := api.FQDNSelector{MatchName: "cilium.io"}
@@ -335,8 +339,8 @@ func (ds *SelectorCacheTestSuite) TestFQDNSelectorUpdates(c *C) {
 }
 
 func (ds *SelectorCacheTestSuite) TestRemoveIdentitiesFQDNSelectors(c *C) {
-	sc := NewSelectorCache(cache.IdentityCache{})
-	sc.SetLocalIdentityNotifier(newDummyIdentityNotifier())
+	sc := testNewSelectorCache(cache.IdentityCache{})
+
 	// Add some identities to the identity cache
 	googleSel := api.FQDNSelector{MatchName: "google.com"}
 	ciliumSel := api.FQDNSelector{MatchName: "cilium.io"}
@@ -383,7 +387,8 @@ func (ds *SelectorCacheTestSuite) TestRemoveIdentitiesFQDNSelectors(c *C) {
 }
 
 func (ds *SelectorCacheTestSuite) TestIdentityUpdatesMultipleUsers(c *C) {
-	sc := NewSelectorCache(cache.IdentityCache{})
+	sc := testNewSelectorCache(cache.IdentityCache{})
+
 	// Add some identities to the identity cache
 	sc.UpdateIdentities(cache.IdentityCache{
 		1234: labels.Labels{"app": labels.NewLabel("app", "test", labels.LabelSourceK8s)}.LabelArray(),
@@ -446,9 +451,11 @@ func (ds *SelectorCacheTestSuite) TestIdentityUpdatesMultipleUsers(c *C) {
 }
 
 func (ds *SelectorCacheTestSuite) TestIdentityNotifier(c *C) {
-	sc := NewSelectorCache(cache.IdentityCache{})
-	idNotifier := newDummyIdentityNotifier()
-	sc.SetLocalIdentityNotifier(idNotifier)
+	sc := testNewSelectorCache(cache.IdentityCache{})
+	idNotifier, ok := sc.localIdentityNotifier.(*testutils.DummyIdentityNotifier)
+	c.Assert(ok, Equals, true)
+	c.Assert(idNotifier, Not(IsNil))
+
 	// Add some identities to the identity cache
 	googleSel := api.FQDNSelector{MatchName: "google.com"}
 	ciliumSel := api.FQDNSelector{MatchName: "cilium.io"}
@@ -458,7 +465,7 @@ func (ds *SelectorCacheTestSuite) TestIdentityNotifier(c *C) {
 	c.Assert(idNotifier.IsRegistered(googleSel), Equals, false)
 
 	injectedIDs := []identity.NumericIdentity{1000, 1001, 1002}
-	idNotifier.injectIdentitiesForSelector(ciliumSel, injectedIDs)
+	idNotifier.InjectIdentitiesForSelector(ciliumSel, injectedIDs)
 
 	// Add a user without adding identities explicitly. The identityNotifier
 	// should have populated them for us.
@@ -500,36 +507,8 @@ func (ds *SelectorCacheTestSuite) TestIdentityNotifier(c *C) {
 
 }
 
-type dummyIdentityNotifier struct {
-	selectors map[api.FQDNSelector][]identity.NumericIdentity
-}
-
-func newDummyIdentityNotifier() *dummyIdentityNotifier {
-	return &dummyIdentityNotifier{
-		selectors: make(map[api.FQDNSelector][]identity.NumericIdentity),
-	}
-}
-
-// RegisterForIdentityUpdates starts managing this selector.
-func (d *dummyIdentityNotifier) RegisterForIdentityUpdates(selector api.FQDNSelector) (identities []identity.NumericIdentity) {
-	ids, ok := d.selectors[selector]
-	if !ok {
-		d.selectors[selector] = []identity.NumericIdentity{}
-	}
-	return ids
-}
-
-// UnregisterForIdentityUpdates stops managing this selector.
-func (d *dummyIdentityNotifier) UnregisterForIdentityUpdates(selector api.FQDNSelector) {
-	delete(d.selectors, selector)
-}
-
-func (d *dummyIdentityNotifier) injectIdentitiesForSelector(fqdnSel api.FQDNSelector, ids []identity.NumericIdentity) {
-	d.selectors[fqdnSel] = ids
-}
-
-// IsRegistered returns whether this selector is being managed.
-func (d *dummyIdentityNotifier) IsRegistered(selector api.FQDNSelector) bool {
-	_, ok := d.selectors[selector]
-	return ok
+func testNewSelectorCache(ids cache.IdentityCache) *SelectorCache {
+	sc := NewSelectorCache(ids)
+	sc.SetLocalIdentityNotifier(testutils.NewDummyIdentityNotifier())
+	return sc
 }
diff --git a/pkg/testutils/identitynotifier.go b/pkg/testutils/identitynotifier.go
new file mode 100644
index 0000000000..a1708afac4
--- /dev/null
+++ b/pkg/testutils/identitynotifier.go
@@ -0,0 +1,68 @@
+// Copyright 2020 Authors of Cilium
+//
+// 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 testutils
+
+import (
+	"github.com/cilium/cilium/pkg/identity"
+	"github.com/cilium/cilium/pkg/lock"
+	"github.com/cilium/cilium/pkg/policy/api"
+)
+
+type DummyIdentityNotifier struct {
+	mutex     lock.Mutex
+	selectors map[api.FQDNSelector][]identity.NumericIdentity
+}
+
+func NewDummyIdentityNotifier() *DummyIdentityNotifier {
+	return &DummyIdentityNotifier{
+		selectors: make(map[api.FQDNSelector][]identity.NumericIdentity),
+	}
+}
+
+// Lock must be held during any calls to RegisterForIdentityUpdatesLocked or
+// UnregisterForIdentityUpdatesLocked.
+func (d *DummyIdentityNotifier) Lock() {
+	d.mutex.Lock()
+}
+
+// Unlock must be called after calls to RegisterForIdentityUpdatesLocked or
+// UnregisterForIdentityUpdatesLocked are done.
+func (d *DummyIdentityNotifier) Unlock() {
+	d.mutex.Unlock()
+}
+
+// RegisterForIdentityUpdatesLocked starts managing this selector.
+func (d *DummyIdentityNotifier) RegisterForIdentityUpdatesLocked(selector api.FQDNSelector) (identities []identity.NumericIdentity) {
+	ids, ok := d.selectors[selector]
+	if !ok {
+		d.selectors[selector] = []identity.NumericIdentity{}
+	}
+	return ids
+}
+
+// UnregisterForIdentityUpdatesLocked stops managing this selector.
+func (d *DummyIdentityNotifier) UnregisterForIdentityUpdatesLocked(selector api.FQDNSelector) {
+	delete(d.selectors, selector)
+}
+
+func (d *DummyIdentityNotifier) InjectIdentitiesForSelector(fqdnSel api.FQDNSelector, ids []identity.NumericIdentity) {
+	d.selectors[fqdnSel] = ids
+}
+
+// IsRegistered returns whether this selector is being managed.
+func (d *DummyIdentityNotifier) IsRegistered(selector api.FQDNSelector) bool {
+	_, ok := d.selectors[selector]
+	return ok
+}