You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by ki...@apache.org on 2020/07/21 04:56:56 UTC

[trafficserver-ingress-controller] branch master updated: Adds boilerplate code for testing

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

kichan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new cb7fcef  Adds boilerplate code for testing
     new 380e3c4  Merge pull request #10 from rishabhc/cicd_pipeline
cb7fcef is described below

commit cb7fcefc55cd906ab3ecc0306744b6e8678c3885
Author: Rishabh Chhabra <ri...@gmail.com>
AuthorDate: Mon Jun 22 22:48:41 2020 -0500

    Adds boilerplate code for testing
    
    Completes one basic test for adding services in redis
    
    Adds function to fetch all key value pairs in redis db one
    
    Adds another path for testing and adds checking for key value map equality
    
    Adds tests for add and update with annotation
    
    Adds TLS tests and updates update tests
    
    Adds tests for handlerIngress for namespaces
    
    Adds tests for handlerEndpoint
    
    Adds tests for endpoint handler
    
    Adds initial tests for reddit
    
    Adds all tests for Redis
    
    Adds lua unit tests for plugin
    
    Modifies clientset to use interface to enable testing
    
    Adds test for configmap handler
    
    Adds method to get ATS config
    
    Modifies watcher to be compatible to unit testing. Adds watcher tests
    
    Adds function to test for map similarity
    
    Replaces deepequal check with map similarity check
    
    Adds configmaps informer to watcher
    
    Adds tests for configmap informers
    
    Adds license to new files
    
    Modifies test to implement unit testing without the need for external libraries
    
    Adds mock for redis for unit testing
    
    Adds fakeATSmanager
    
    Modifies configmap tests to use fakeATS
    
    Modifies watcher to use fake ATS
    
    Gets namespace from ATS Interface
    
    Adds instructions to run unit tests
    
    Adds breaks in README
    
    adds warning to readme
    
    adds warning to readme
---
 README.md                        |  12 ++
 endpoint/endpoint.go             |   2 +-
 go.mod                           |   1 +
 namespace/namespace.go           |   4 +
 pluginats/connect_redis_test.lua | 153 ++++++++++++++
 proxy/ats.go                     |  18 ++
 proxy/{ats.go => fakeATS.go}     |  27 ++-
 redis/redis.go                   | 107 ++++++++++
 redis/redis_test.go              | 199 +++++++++++++++++++
 util/util.go                     |  62 ++++++
 watcher/handlerConfigmap_test.go | 145 ++++++++++++++
 watcher/handlerEndpoint_test.go  | 256 ++++++++++++++++++++++++
 watcher/handlerIngress_test.go   | 400 +++++++++++++++++++++++++++++++++++++
 watcher/watcher.go               |  54 +++--
 watcher/watcher_test.go          | 419 +++++++++++++++++++++++++++++++++++++++
 15 files changed, 1832 insertions(+), 27 deletions(-)

diff --git a/README.md b/README.md
index 165d13a..05cde09 100644
--- a/README.md
+++ b/README.md
@@ -291,6 +291,18 @@ Use the following steps to install Prometheus and Grafana and use them to monito
 ### Compilation
 To compile, while in `ingress-ats/` directory: `go build -o ingress_ats main/main.go`
 
+### Unit Tests
+The project includes unit tests for the controller written in Golang and the plugin written in Lua.
+
+To run the Golang unit tests: `go test ./watcher/ && go test ./redis/`
+
+The Lua unit tests use `busted` for testing. `busted` can be installed using `luarocks`:`luarocks install busted`. More information on how to install busted is available [here](https://olivinelabs.com/busted/). 
+> :warning: **Note that the project uses Lua 5.1 version**
+
+To run the Lua unit tests: 
+- `cd pluginats`
+- `busted connect_redis_test.lua` 
+
 ### Text-Editor
 The repository comes with basic support for both [vscode](https://code.visualstudio.com/) and `vim`. 
 
diff --git a/endpoint/endpoint.go b/endpoint/endpoint.go
index 516fa57..73fc74d 100644
--- a/endpoint/endpoint.go
+++ b/endpoint/endpoint.go
@@ -43,6 +43,6 @@ const (
 // Endpoint stores all essential information to act on HostGroups
 type Endpoint struct {
 	RedisClient *redis.Client
-	ATSManager  *proxy.ATSManager
+	ATSManager  proxy.ATSManagerInterface
 	NsManager   *namespace.NsManager
 }
diff --git a/go.mod b/go.mod
index 3dc085c..b04380f 100644
--- a/go.mod
+++ b/go.mod
@@ -17,4 +17,5 @@ require (
 	k8s.io/client-go v0.0.0-20190626045420-1ec4b74c7bda
 	k8s.io/klog v0.3.3 // indirect
 	k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a // indirect
+	github.com/alicebob/miniredis/v2 v2.13.0
 )
diff --git a/namespace/namespace.go b/namespace/namespace.go
index e6030e3..7b09ac0 100644
--- a/namespace/namespace.go
+++ b/namespace/namespace.go
@@ -51,3 +51,7 @@ func (m *NsManager) Init() {
 		m.allNamespaces = false
 	}
 }
+
+func (m *NsManager) DisableAllNamespaces() {
+	m.allNamespaces = false
+}
diff --git a/pluginats/connect_redis_test.lua b/pluginats/connect_redis_test.lua
new file mode 100644
index 0000000..008479f
--- /dev/null
+++ b/pluginats/connect_redis_test.lua
@@ -0,0 +1,153 @@
+--  Licensed to the Apache Software Foundation (ASF) under one
+--  or more contributor license agreements.  See the NOTICE file
+--  distributed with this work for additional information
+--  regarding copyright ownership.  The ASF licenses this file
+--  to you 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.
+
+_G.ts = { client_request = {}, http = {} }
+_G.client = {dbone = {}, dbdefault = {}, selecteddb = 0}
+_G.TS_LUA_REMAP_DID_REMAP = 1
+
+function ts.client_request.get_url_scheme()
+    return 'http'
+end
+
+function ts.client_request.get_url_host()
+    return 'test.edge.com'
+end
+
+function ts.client_request.get_uri()
+    return '/app1'
+end
+function ts.client_request.get_url_port()
+    return '80'
+end
+
+function connect(path)
+  return client
+end
+
+function client.select(self, number)
+  if number == 1 then
+    self.selecteddb = 1
+  elseif number == 0 then
+    self.selecteddb = 0
+  end
+end
+
+function client.sadd(self, key, ...)
+  db = nil
+  if self.selecteddb == 1 then 
+    db = self.dbone
+  elseif self.selecteddb == 0 then
+    db = self.dbdefault
+  end
+  
+  if type(db[key]) ~= "table" then
+    db[key] = {}
+  end
+
+  for i=1,select('#',...) do   
+    local tmp = select(i,...)
+    table.insert(db[key],tmp)
+  end
+end
+
+function client.ping()
+  return "PONG"
+end
+
+function client.smembers(self, key)
+  db = nil
+  if self.selecteddb == 1 then 
+    db = self.dbone
+  elseif self.selecteddb == 0 then
+    db = self.dbdefault
+  end
+
+  return db[key]
+end
+
+function client.srandmember(self, key)
+  idx = math.random(1,2)
+  db = nil
+  if self.selecteddb == 1 then 
+    db = self.dbone
+  elseif self.selecteddb == 0 then
+    db = self.dbdefault
+  end
+  
+  return db[key][idx]
+end
+  
+
+describe("Unit tests - Lua", function()
+  describe("Ingress Controller", function()
+
+    setup(function()
+      local match = require("luassert.match")
+
+      package.loaded.redis = nil
+      local redis = {}
+      redis.connect = {}
+      redis.connect = connect
+      package.preload['redis'] = function () 
+        return redis
+      end
+
+      client = redis.connect()
+
+      client:select(1)
+      client:sadd("http://test.edge.com/app1","trafficserver-test-2:appsvc1:8080")
+      client:select(0)
+      client:sadd("trafficserver-test-2:appsvc1:8080","172.17.0.3#8080#http","172.17.0.5#8080#http")
+      --require 'pl.pretty'.dump(client)
+
+      stub(ts, "add_package_cpath")
+      stub(ts, "add_package_path")
+      stub(ts, "debug")
+      stub(ts, "error")
+      stub(ts.client_request, "set_url_host")
+      stub(ts.client_request, "set_url_port")
+      stub(ts.client_request, "set_url_scheme")
+      stub(ts.client_request, "set_uri")
+      stub(ts.http, "skip_remapping_set")
+      stub(ts.http, "set_resp")
+    end)
+
+    it("Test - Redirect to correct IP", function()
+      require("connect_redis")
+      local result = do_global_read_request()
+
+      assert.stub(ts.client_request.set_url_host).was.called_with(match.is_any_of(match.is_same("172.17.0.3"),match.is_same("172.17.0.5"))) 
+      assert.stub(ts.client_request.set_url_port).was.called_with("8080")
+      assert.stub(ts.client_request.set_url_scheme).was.called_with("http")
+    end)
+
+    it("Test - Snippet", function()
+      client:select(1)
+      client:sadd("http://test.edge.com/app1","$trafficserver-test-3/app-ingress/411990")
+      snippet = "ts.debug('Debug msg example')\nts.error('Error msg example')\n-- ts.hook(TS_LUA_HOOK_SEND_RESPONSE_HDR, function()\n--   ts.client_response.header['Location'] = 'https://test.edge.com/app2'\n-- end)\nts.http.skip_remapping_set(0)\nts.http.set_resp(301, 'Redirect')\nts.debug('Uncomment the above lines to redirect http request to https')\nts.debug('Modification for testing')\n"
+      client:sadd("$trafficserver-test-3/app-ingress/411990",snippet) 
+
+      --require 'pl.pretty'.dump(client)
+      require "connect_redis"
+      local result = do_global_read_request()
+
+      assert.stub(ts.error).was.called_with("Error msg example")
+      assert.stub(ts.http.skip_remapping_set).was.called_with(0)
+      assert.stub(ts.http.set_resp).was.called_with(301,"Redirect")
+    end)
+
+  end)
+end)
\ No newline at end of file
diff --git a/proxy/ats.go b/proxy/ats.go
index 29bc7da..3e9eca0 100644
--- a/proxy/ats.go
+++ b/proxy/ats.go
@@ -18,11 +18,18 @@ package proxy
 import (
 	"fmt"
 	"os/exec"
+	"strings"
 )
 
 // ATSManager talks to ATS
 // In the future, this is the struct that should manage
 // everything related to ATS
+type ATSManagerInterface interface {
+	ConfigSet(k, v string) (string, error)
+	ConfigGet(k string) (string, error)
+	IncludeIngressClass(c string) bool
+}
+
 type ATSManager struct {
 	Namespace    string
 	IngressClass string
@@ -50,3 +57,14 @@ func (m *ATSManager) ConfigSet(k, v string) (msg string, err error) {
 	}
 	return fmt.Sprintf("Ran p.Key: %s p.Val: %s --> stdoutStderr: %q", k, v, stdoutStderr), nil
 }
+
+func (m *ATSManager) ConfigGet(k string) (msg string, err error) {
+	cmd := exec.Command("traffic_ctl", "config", "get", k)
+	stdoutStderr, err := cmd.CombinedOutput()
+	if err != nil {
+		return "", fmt.Errorf("failed to execute: traffic_ctl config get %s Error: %s", k, err.Error())
+	}
+	stdoutString := fmt.Sprintf("%q", stdoutStderr)
+	configValue := strings.Split(strings.Trim(strings.Trim(stdoutString, "\""), "\\n"), ": ")[1]
+	return configValue, err
+}
diff --git a/proxy/ats.go b/proxy/fakeATS.go
similarity index 51%
copy from proxy/ats.go
copy to proxy/fakeATS.go
index 29bc7da..f9dbc8c 100644
--- a/proxy/ats.go
+++ b/proxy/fakeATS.go
@@ -16,19 +16,17 @@
 package proxy
 
 import (
+	"errors"
 	"fmt"
-	"os/exec"
 )
 
-// ATSManager talks to ATS
-// In the future, this is the struct that should manage
-// everything related to ATS
-type ATSManager struct {
+type FakeATSManager struct {
 	Namespace    string
 	IngressClass string
+	Config       map[string]string
 }
 
-func (m *ATSManager) IncludeIngressClass(c string) bool {
+func (m *FakeATSManager) IncludeIngressClass(c string) bool {
 	if m.IngressClass == "" {
 		return true
 	}
@@ -40,13 +38,14 @@ func (m *ATSManager) IncludeIngressClass(c string) bool {
 	return false
 }
 
-// ConfigSet configures reloadable ATS config. When there is no error,
-// a message string is returned
-func (m *ATSManager) ConfigSet(k, v string) (msg string, err error) {
-	cmd := exec.Command("traffic_ctl", "config", "set", k, v)
-	stdoutStderr, err := cmd.CombinedOutput()
-	if err != nil {
-		return "", fmt.Errorf("failed to execute: traffic_ctl config set %s %s Error: %s", k, v, err.Error())
+func (m *FakeATSManager) ConfigSet(k, v string) (msg string, err error) {
+	m.Config[k] = v
+	return fmt.Sprintf("Ran p.Key: %s p.Val: %s", k, v), nil
+}
+
+func (m *FakeATSManager) ConfigGet(k string) (msg string, err error) {
+	if val, ok := m.Config[k]; ok {
+		return val, nil
 	}
-	return fmt.Sprintf("Ran p.Key: %s p.Val: %s --> stdoutStderr: %q", k, v, stdoutStderr), nil
+	return "", errors.New("key does not exist")
 }
diff --git a/redis/redis.go b/redis/redis.go
index c68ed79..1661137 100644
--- a/redis/redis.go
+++ b/redis/redis.go
@@ -21,6 +21,7 @@ import (
 
 	//	"sync"
 
+	"github.com/alicebob/miniredis/v2"
 	"github.com/go-redis/redis"
 )
 
@@ -55,6 +56,37 @@ func Init() (*Client, error) {
 	return rClient, nil
 }
 
+func InitForTesting() (*Client, error) {
+	mr, err := miniredis.Run()
+
+	if err != nil {
+		return nil, err
+	}
+
+	defaultDB := redis.NewClient(&redis.Options{
+		Addr: mr.Addr(), // connect to domain socket
+		DB:   0,         // use default DB
+	})
+
+	_, err = defaultDB.Ping().Result()
+	if err != nil {
+		return nil, err
+	}
+
+	dbOne := redis.NewClient(&redis.Options{
+		Addr: mr.Addr(), // connect to domain socket
+		DB:   1,         // use DB number 1
+	})
+
+	_, err = dbOne.Ping().Result()
+	if err != nil {
+		return nil, err
+	}
+
+	return &Client{defaultDB, dbOne}, nil
+
+}
+
 // CreateRedisClient establishes connection to redis DB
 func CreateRedisClient() (*Client, error) {
 	defaultDB := redis.NewClient(&redis.Options{
@@ -116,6 +148,7 @@ func (c *Client) DefaultDBSUnionStore(dest, src string) {
 // DBOneSAdd does SAdd on DB One and logs results
 func (c *Client) DBOneSAdd(hostport, svcport string) {
 	_, err := c.DBOne.SAdd(hostport, svcport).Result()
+
 	if err != nil {
 		log.Printf("DBOne.SAdd(%s, %s).Result() Error: %s\n", hostport, svcport, err.Error())
 	}
@@ -189,3 +222,77 @@ func (c *Client) PrintAllKeys() {
 		log.Println("DBOne.Do(\"KEYS\", \"*\").Result(): ", res)
 	}
 }
+
+func (c *Client) GetDefaultDBKeyValues() map[string][]string {
+	var (
+		res         interface{}
+		err         error
+		keyValueMap map[string][]string
+	)
+
+	if res, err = c.DefaultDB.Do("KEYS", "*").Result(); err != nil {
+		log.Println("Error Printing DB One (1): ", err)
+	}
+
+	keyValueMap = make(map[string][]string)
+
+	switch keys := res.(type) {
+	case []interface{}:
+		for _, key := range keys {
+			if smembers, err := c.DefaultDB.Do("SMEMBERS", key).Result(); err != nil {
+				log.Println("Error Printing DB One (1): ", err)
+			} else {
+				keyValueMap[key.(string)] = []string{}
+				switch values := smembers.(type) {
+				case []interface{}:
+					for _, value := range values {
+						keyValueMap[key.(string)] = append(keyValueMap[key.(string)], value.(string))
+					}
+				default:
+					fmt.Printf("Cannot iterate over %T\n", smembers)
+				}
+			}
+		}
+	default:
+		fmt.Printf("Cannot iterate over %T\n", res)
+	}
+
+	return keyValueMap
+}
+
+func (c *Client) GetDBOneKeyValues() map[string][]string {
+	var (
+		res         interface{}
+		err         error
+		keyValueMap map[string][]string
+	)
+
+	if res, err = c.DBOne.Do("KEYS", "*").Result(); err != nil {
+		log.Println("Error Printing DB One (1): ", err)
+	}
+
+	keyValueMap = make(map[string][]string)
+
+	switch keys := res.(type) {
+	case []interface{}:
+		for _, key := range keys {
+			if smembers, err := c.DBOne.Do("SMEMBERS", key).Result(); err != nil {
+				log.Println("Error Printing DB One (1): ", err)
+			} else {
+				keyValueMap[key.(string)] = []string{}
+				switch values := smembers.(type) {
+				case []interface{}:
+					for _, value := range values {
+						keyValueMap[key.(string)] = append(keyValueMap[key.(string)], value.(string))
+					}
+				default:
+					fmt.Printf("Cannot iterate over %T\n", smembers)
+				}
+			}
+		}
+	default:
+		fmt.Printf("Cannot iterate over %T\n", res)
+	}
+
+	return keyValueMap
+}
diff --git a/redis/redis_test.go b/redis/redis_test.go
new file mode 100644
index 0000000..4b31239
--- /dev/null
+++ b/redis/redis_test.go
@@ -0,0 +1,199 @@
+/*
+
+   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 redis
+
+import (
+	"ingress-ats/util"
+	"testing"
+)
+
+func TestInit(t *testing.T) {
+	_, err := InitForTesting()
+	if err != nil {
+		t.Error(err)
+	}
+}
+
+func TestFlush(t *testing.T) {
+	rClient, _ := InitForTesting()
+	rClient.DefaultDB.SAdd("test-key", "test-val")
+	rClient.DefaultDB.SAdd("test-key", "test-val-2")
+
+	err := rClient.Flush()
+	if err != nil {
+		t.Error(err)
+	}
+
+	returnedKeys := rClient.GetDefaultDBKeyValues()
+	expectedKeys := make(map[string][]string)
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestGetDefaultDBKeyValues(t *testing.T) {
+	rClient, _ := InitForTesting()
+
+	rClient.DefaultDB.SAdd("test-key", "test-val")
+	rClient.DefaultDB.SAdd("test-key", "test-val-2")
+	rClient.DefaultDB.SAdd("test-key-2", "test-val")
+
+	returnedKeys := rClient.GetDefaultDBKeyValues()
+	expectedKeys := getExpectedKeysForAdd()
+	expectedKeys["test-key"] = append([]string{"test-val-2"}, expectedKeys["test-key"]...)
+	expectedKeys["test-key-2"] = make([]string, 1)
+	expectedKeys["test-key-2"][0] = "test-val"
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestGetDBOneKeyValues(t *testing.T) {
+	rClient, _ := InitForTesting()
+
+	rClient.DBOne.SAdd("test-key", "test-val")
+	rClient.DBOne.SAdd("test-key", "test-val-2")
+	rClient.DBOne.SAdd("test-key-2", "test-val")
+
+	returnedKeys := rClient.GetDBOneKeyValues()
+	expectedKeys := getExpectedKeysForAdd()
+	expectedKeys["test-key"] = append([]string{"test-val-2"}, expectedKeys["test-key"]...)
+	expectedKeys["test-key-2"] = make([]string, 1)
+	expectedKeys["test-key-2"][0] = "test-val"
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestDefaultDBSAdd(t *testing.T) {
+	rClient, _ := InitForTesting()
+
+	rClient.DefaultDBSAdd("test-key", "test-val")
+	returnedKeys := rClient.GetDefaultDBKeyValues()
+	expectedKeys := getExpectedKeysForAdd()
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestDefaultDBDel(t *testing.T) {
+	rClient, _ := InitForTesting()
+
+	rClient.DefaultDBSAdd("test-key", "test-val")
+	rClient.DefaultDBSAdd("test-key-2", "test-val-2")
+	rClient.DefaultDBDel("test-key")
+
+	returnedKeys := rClient.GetDefaultDBKeyValues()
+	expectedKeys := getExpectedKeysForAdd()
+	delete(expectedKeys, "test-key")
+	expectedKeys["test-key-2"] = make([]string, 1)
+	expectedKeys["test-key-2"][0] = "test-val-2"
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestDefaultDBSUnionStore(t *testing.T) {
+	rClient, _ := InitForTesting()
+
+	rClient.DefaultDBSAdd("test-key", "test-val")
+	rClient.DefaultDBSAdd("test-key-2", "test-val-2")
+	rClient.DefaultDBSUnionStore("test-key", "test-key-2")
+
+	returnedKeys := rClient.GetDefaultDBKeyValues()
+	expectedKeys := getExpectedKeysForAdd()
+	expectedKeys["test-key"][0] = "test-val-2"
+	expectedKeys["test-key-2"] = make([]string, 1)
+	expectedKeys["test-key-2"][0] = "test-val-2"
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestDBOneSAdd(t *testing.T) {
+	rClient, _ := InitForTesting()
+
+	rClient.DBOneSAdd("test-key", "test-val")
+	returnedKeys := rClient.GetDBOneKeyValues()
+	expectedKeys := getExpectedKeysForAdd()
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestDBOneSRem(t *testing.T) {
+	rClient, _ := InitForTesting()
+
+	rClient.DBOneSAdd("test-key", "test-val")
+	rClient.DBOneSAdd("test-key", "test-val-2")
+	rClient.DBOneSAdd("test-key", "test-val-3")
+	rClient.DBOneSRem("test-key", "test-val-2")
+	returnedKeys := rClient.GetDBOneKeyValues()
+	expectedKeys := getExpectedKeysForAdd()
+	expectedKeys["test-key"] = append([]string{"test-val-3"}, expectedKeys["test-key"]...)
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestDBOneDel(t *testing.T) {
+	rClient, _ := InitForTesting()
+
+	rClient.DBOneSAdd("test-key", "test-val")
+	rClient.DBOneSAdd("test-key-2", "test-val-2")
+	rClient.DBOneDel("test-key")
+
+	returnedKeys := rClient.GetDBOneKeyValues()
+	expectedKeys := getExpectedKeysForAdd()
+	delete(expectedKeys, "test-key")
+	expectedKeys["test-key-2"] = make([]string, 1)
+	expectedKeys["test-key-2"][0] = "test-val-2"
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestDBOneSUnionStore(t *testing.T) {
+	rClient, _ := InitForTesting()
+
+	rClient.DBOneSAdd("test-key", "test-val")
+	rClient.DBOneSAdd("test-key-2", "test-val-2")
+	rClient.DBOneSUnionStore("test-key", "test-key-2")
+
+	returnedKeys := rClient.GetDBOneKeyValues()
+	expectedKeys := getExpectedKeysForAdd()
+	expectedKeys["test-key"][0] = "test-val-2"
+	expectedKeys["test-key-2"] = make([]string, 1)
+	expectedKeys["test-key-2"][0] = "test-val-2"
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func getExpectedKeysForAdd() map[string][]string {
+	expectedKeys := make(map[string][]string)
+	expectedKeys["test-key"] = make([]string, 1)
+	expectedKeys["test-key"][0] = "test-val"
+	return expectedKeys
+}
diff --git a/util/util.go b/util/util.go
index 587e62a..ae2afb7 100644
--- a/util/util.go
+++ b/util/util.go
@@ -151,3 +151,65 @@ func ExtractIngressClass(ann map[string]string) (class string, err error) {
 func FmtMarshalled(marshalled []byte) string {
 	return fmt.Sprintf("%q", marshalled)
 }
+
+func ReverseSlice(s []string) []string {
+	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
+		s[i], s[j] = s[j], s[i]
+	}
+
+	return s
+}
+
+func IsSameMap(x, y map[string][]string) bool {
+	if len(x) != len(y) {
+		return false
+	}
+
+	keysInX := make([]string, 0, len(x))
+	for k := range x {
+		keysInX = append(keysInX, k)
+	}
+
+	keysInY := make([]string, 0, len(y))
+	for k := range y {
+		keysInY = append(keysInY, k)
+	}
+
+	if !IsSameSlice(keysInX, keysInY) {
+		return false
+	}
+
+	for k := range x {
+		if !IsSameSlice(x[k], y[k]) {
+			return false
+		}
+	}
+
+	return true
+}
+
+func IsSameSlice(x, y []string) bool {
+	if len(x) != len(y) {
+		return false
+	}
+	// create a map of string -> int
+	diff := make(map[string]int, len(x))
+	for _, _x := range x {
+		// 0 value for int is 0, so just increment a counter for the string
+		diff[_x]++
+	}
+	for _, _y := range y {
+		// If the string _y is not in diff bail out early
+		if _, ok := diff[_y]; !ok {
+			return false
+		}
+		diff[_y] -= 1
+		if diff[_y] == 0 {
+			delete(diff, _y)
+		}
+	}
+	if len(diff) == 0 {
+		return true
+	}
+	return false
+}
diff --git a/watcher/handlerConfigmap_test.go b/watcher/handlerConfigmap_test.go
new file mode 100644
index 0000000..f952fc5
--- /dev/null
+++ b/watcher/handlerConfigmap_test.go
@@ -0,0 +1,145 @@
+/*
+
+   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 watcher
+
+import (
+	ep "ingress-ats/endpoint"
+	"ingress-ats/namespace"
+	"ingress-ats/proxy"
+	"ingress-ats/redis"
+	"log"
+	"reflect"
+	"testing"
+
+	v1 "k8s.io/api/core/v1"
+	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestAdd_BasicConfigMap(t *testing.T) {
+	cmHandler := createExampleCMHandler()
+	exampleConfigMap := createExampleConfigMap()
+
+	cmHandler.Add(&exampleConfigMap)
+
+	rEnabled, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_enabled")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(rEnabled, "1") {
+		t.Errorf("returned \n%s,  but expected \n%s", rEnabled, "1")
+	}
+
+	rInterval, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_interval_sec")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(rInterval, "3000") {
+		t.Errorf("returned \n%s,  but expected \n%s", rInterval, "3000")
+	}
+
+	threshold, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.restart.active_client_threshold")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(threshold, "0") {
+		t.Errorf("returned \n%s,  but expected \n%s", threshold, "0")
+	}
+
+}
+
+func TestUpdate_BasicConfigMap(t *testing.T) {
+	cmHandler := createExampleCMHandler()
+	exampleConfigMap := createExampleConfigMap()
+	exampleConfigMap.Data["proxy.config.output.logfile.rolling_interval_sec"] = "2000"
+
+	cmHandler.update(&exampleConfigMap)
+
+	rEnabled, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_enabled")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(rEnabled, "1") {
+		t.Errorf("returned \n%s,  but expected \n%s", rEnabled, "1")
+	}
+
+	rInterval, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_interval_sec")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(rInterval, "2000") {
+		t.Errorf("returned \n%s,  but expected \n%s", rInterval, "2000")
+	}
+
+	threshold, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.restart.active_client_threshold")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(threshold, "0") {
+		t.Errorf("returned \n%s,  but expected \n%s", threshold, "0")
+	}
+
+}
+
+func createExampleConfigMap() v1.ConfigMap {
+	exampleConfigMap := v1.ConfigMap{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "testsvc",
+			Namespace: "trafficserver-test-2",
+		},
+		Data: map[string]string{
+			"proxy.config.output.logfile.rolling_enabled":      "1",
+			"proxy.config.output.logfile.rolling_interval_sec": "3000",
+			"proxy.config.restart.active_client_threshold":     "0",
+		},
+	}
+
+	return exampleConfigMap
+}
+
+func createExampleCMHandler() CMHandler {
+	exampleEndpoint := createExampleEndpointWithFakeATS()
+	cmHandler := CMHandler{"configmap", &exampleEndpoint}
+
+	return cmHandler
+}
+
+func createExampleEndpointWithFakeATS() ep.Endpoint {
+	rClient, err := redis.InitForTesting()
+	if err != nil {
+		log.Panicln("Redis Error: ", err)
+	}
+
+	namespaceMap := make(map[string]bool)
+	ignoreNamespaceMap := make(map[string]bool)
+
+	nsManager := namespace.NsManager{
+		NamespaceMap:       namespaceMap,
+		IgnoreNamespaceMap: ignoreNamespaceMap,
+	}
+
+	nsManager.Init()
+
+	exampleEndpoint := ep.Endpoint{
+		RedisClient: rClient,
+		ATSManager: &proxy.FakeATSManager{
+			Namespace:    "default",
+			IngressClass: "",
+			Config:       make(map[string]string),
+		},
+		NsManager: &nsManager,
+	}
+
+	return exampleEndpoint
+}
diff --git a/watcher/handlerEndpoint_test.go b/watcher/handlerEndpoint_test.go
new file mode 100644
index 0000000..7872a93
--- /dev/null
+++ b/watcher/handlerEndpoint_test.go
@@ -0,0 +1,256 @@
+/*
+
+   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 watcher
+
+import (
+	"ingress-ats/util"
+	"testing"
+
+	v1 "k8s.io/api/core/v1"
+	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestAdd_BasicEndpoint(t *testing.T) {
+	epHandler := createExampleEpHandler()
+	exampleV1Endpoint := createExampleV1Endpoint()
+
+	epHandler.add(&exampleV1Endpoint)
+
+	returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues()
+
+	expectedKeys := getExpectedKeysForEndpointAdd()
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestAdd_IgnoreEndpointNamespace(t *testing.T) {
+	epHandler := createExampleEpHandler()
+	exampleV1Endpoint := createExampleV1Endpoint()
+
+	epHandler.Ep.NsManager.IgnoreNamespaceMap["trafficserver-test-2"] = true
+
+	epHandler.add(&exampleV1Endpoint)
+
+	returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues()
+
+	expectedKeys := make(map[string][]string)
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestUpdate_UpdateAddress(t *testing.T) {
+	epHandler := createExampleEpHandler()
+	exampleV1Endpoint := createExampleV1Endpoint()
+
+	epHandler.add(&exampleV1Endpoint)
+
+	exampleV1Endpoint.Subsets[0].Addresses[0].IP = "10.10.3.3"
+
+	epHandler.update(&exampleV1Endpoint)
+
+	returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues()
+
+	expectedKeys := getExpectedKeysForAddressUpdate()
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestUpdate_UpdatePortNumber(t *testing.T) {
+	epHandler := createExampleEpHandler()
+	exampleV1Endpoint := createExampleV1Endpoint()
+
+	epHandler.add(&exampleV1Endpoint)
+
+	exampleV1Endpoint.Subsets[0].Ports[0].Port = 8081
+
+	epHandler.update(&exampleV1Endpoint)
+
+	returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues()
+
+	expectedKeys := getExpectedKeysForEndpointAdd()
+	expectedKeys["trafficserver-test-2:testsvc:8081"] = make([]string, 2)
+	expectedKeys["trafficserver-test-2:testsvc:8081"][0] = "10.10.2.2#8081#http"
+	expectedKeys["trafficserver-test-2:testsvc:8081"][1] = "10.10.1.1#8081#http"
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestUpdate_UpdatePortName(t *testing.T) {
+	epHandler := createExampleEpHandler()
+	exampleV1Endpoint := createExampleV1Endpoint()
+
+	epHandler.add(&exampleV1Endpoint)
+
+	exampleV1Endpoint.Subsets[0].Ports[0].Name = "https"
+
+	epHandler.update(&exampleV1Endpoint)
+
+	returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues()
+
+	expectedKeys := getExpectedKeysForEndpointAdd()
+	expectedKeys["trafficserver-test-2:testsvc:8080"] = make([]string, 2)
+	expectedKeys["trafficserver-test-2:testsvc:8080"][0] = "10.10.1.1#8080#https"
+	expectedKeys["trafficserver-test-2:testsvc:8080"][1] = "10.10.2.2#8080#https"
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestUpdate_UpdateEndpointName(t *testing.T) {
+	epHandler := createExampleEpHandler()
+	exampleV1Endpoint := createExampleV1Endpoint()
+
+	epHandler.add(&exampleV1Endpoint)
+
+	exampleV1Endpoint.ObjectMeta.Name = "testsvc-modified"
+
+	epHandler.update(&exampleV1Endpoint)
+
+	returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues()
+
+	expectedKeys := getExpectedKeysForEndpointAdd()
+	expectedKeys["trafficserver-test-2:testsvc-modified:8080"] = expectedKeys["trafficserver-test-2:testsvc:8080"]
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestDelete_DeleteEndpoint(t *testing.T) {
+	epHandler := createExampleEpHandler()
+	exampleV1Endpoint := createExampleV1Endpoint()
+
+	epHandler.add(&exampleV1Endpoint)
+	epHandler.delete(&exampleV1Endpoint)
+
+	returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues()
+
+	expectedKeys := make(map[string][]string)
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestUpdate_DeleteAddress(t *testing.T) {
+	epHandler := createExampleEpHandler()
+	exampleV1Endpoint := createExampleV1Endpoint()
+
+	epHandler.add(&exampleV1Endpoint)
+
+	exampleV1Endpoint.Subsets[0].Addresses = exampleV1Endpoint.Subsets[0].Addresses[:1]
+
+	epHandler.update(&exampleV1Endpoint)
+
+	returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues()
+
+	expectedKeys := getExpectedKeysForEndpointAdd()
+	expectedKeys["trafficserver-test-2:testsvc:8080"] = expectedKeys["trafficserver-test-2:testsvc:8080"][:1]
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestUpdate_AddAddress(t *testing.T) {
+	epHandler := createExampleEpHandler()
+	exampleV1Endpoint := createExampleV1Endpoint()
+
+	epHandler.add(&exampleV1Endpoint)
+
+	exampleV1Endpoint.Subsets[0].Addresses = append(exampleV1Endpoint.Subsets[0].Addresses, v1.EndpointAddress{
+		IP: "10.10.3.3",
+	})
+
+	epHandler.update(&exampleV1Endpoint)
+
+	returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues()
+
+	expectedKeys := getExpectedKeysForEndpointAdd()
+	expectedKeys["trafficserver-test-2:testsvc:8080"] = append(expectedKeys["trafficserver-test-2:testsvc:8080"], "10.10.3.3#8080#http")
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func createExampleV1Endpoint() v1.Endpoints {
+	exampleEndpoint := v1.Endpoints{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "testsvc",
+			Namespace: "trafficserver-test-2",
+		},
+		Subsets: []v1.EndpointSubset{
+			{
+				Addresses: []v1.EndpointAddress{
+					{
+						IP: "10.10.1.1",
+					},
+					{
+						IP: "10.10.2.2",
+					},
+				},
+				Ports: []v1.EndpointPort{
+					{
+						Name:     "main",
+						Port:     8080,
+						Protocol: "TCP",
+					},
+				},
+			},
+		},
+	}
+
+	return exampleEndpoint
+}
+
+func createExampleEpHandler() EpHandler {
+	exampleEndpoint := createExampleEndpoint()
+	epHandler := EpHandler{"endpoints", &exampleEndpoint}
+
+	return epHandler
+}
+
+func getExpectedKeysForEndpointAdd() map[string][]string {
+	expectedKeys := make(map[string][]string)
+	expectedKeys["trafficserver-test-2:testsvc:8080"] = []string{}
+
+	expectedKeys["trafficserver-test-2:testsvc:8080"] = append(expectedKeys["trafficserver-test-2:testsvc:8080"], "10.10.1.1#8080#http")
+	expectedKeys["trafficserver-test-2:testsvc:8080"] = append(expectedKeys["trafficserver-test-2:testsvc:8080"], "10.10.2.2#8080#http")
+
+	return expectedKeys
+}
+
+func getExpectedKeysForAddressUpdate() map[string][]string {
+	expectedKeys := getExpectedKeysForEndpointAdd()
+
+	if expectedKeys["trafficserver-test-2:testsvc:8080"][0] == "10.10.1.1#8080#http" {
+		expectedKeys["trafficserver-test-2:testsvc:8080"] = expectedKeys["trafficserver-test-2:testsvc:8080"][1:]
+	} else {
+		expectedKeys["trafficserver-test-2:testsvc:8080"] = expectedKeys["trafficserver-test-2:testsvc:8080"][:1]
+	}
+
+	expectedKeys["trafficserver-test-2:testsvc:8080"] = append(expectedKeys["trafficserver-test-2:testsvc:8080"], "10.10.3.3#8080#http")
+
+	return expectedKeys
+}
diff --git a/watcher/handlerIngress_test.go b/watcher/handlerIngress_test.go
new file mode 100644
index 0000000..96c7315
--- /dev/null
+++ b/watcher/handlerIngress_test.go
@@ -0,0 +1,400 @@
+/*
+
+   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 watcher
+
+import (
+	"log"
+	"testing"
+
+	ep "ingress-ats/endpoint"
+	"ingress-ats/namespace"
+	"ingress-ats/proxy"
+	"ingress-ats/redis"
+	"ingress-ats/util"
+
+	v1beta1 "k8s.io/api/extensions/v1beta1"
+	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+)
+
+func TestAdd_ExampleIngress(t *testing.T) {
+	igHandler := createExampleIgHandler()
+	exampleIngress := createExampleIngress()
+
+	igHandler.add(&exampleIngress)
+
+	returnedKeys := igHandler.Ep.RedisClient.GetDBOneKeyValues()
+
+	expectedKeys := getExpectedKeysForAdd()
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestAdd_ExampleIngressWithAnnotation(t *testing.T) {
+	igHandler := createExampleIgHandler()
+	exampleIngress := createExampleIngressWithAnnotation()
+
+	igHandler.add(&exampleIngress)
+
+	returnedKeys := igHandler.Ep.RedisClient.GetDBOneKeyValues()
+
+	expectedKeys := getExpectedKeysForAddWithAnnotation()
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestAdd_ExampleIngressWithTLS(t *testing.T) {
+	igHandler := createExampleIgHandler()
+	exampleIngress := createExampleIngressWithTLS()
+
+	igHandler.add(&exampleIngress)
+
+	returnedKeys := igHandler.Ep.RedisClient.GetDBOneKeyValues()
+
+	expectedKeys := getExpectedKeysForAdd()
+	expectedKeys["https://test.edge.com/app1"] = expectedKeys["http://test.edge.com/app1"]
+	delete(expectedKeys, "http://test.edge.com/app1")
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestAdd_ExampleIngressWithIgnoredNamespace(t *testing.T) {
+	igHandler := createExampleIgHandler()
+	exampleIngress := createExampleIngressWithTLS()
+
+	igHandler.Ep.NsManager.IgnoreNamespaceMap["ignored-namespace"] = true
+
+	exampleIngress.ObjectMeta.Namespace = "ignored-namespace"
+
+	igHandler.add(&exampleIngress)
+
+	returnedKeys := igHandler.Ep.RedisClient.GetDBOneKeyValues()
+
+	expectedKeys := make(map[string][]string)
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestAdd_ExampleIngressWithIncludedNamespace(t *testing.T) {
+	igHandler := createExampleIgHandler()
+	exampleIngress := createExampleIngress()
+
+	igHandler.Ep.NsManager.DisableAllNamespaces()
+	igHandler.Ep.NsManager.NamespaceMap["trafficserver-test"] = true
+
+	igHandler.add(&exampleIngress)
+
+	returnedKeys := igHandler.Ep.RedisClient.GetDBOneKeyValues()
+
+	expectedKeys := getExpectedKeysForAdd()
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+
+}
+
+func TestUpdate_ModifyIngress(t *testing.T) {
+	igHandler := createExampleIgHandler()
+	exampleIngress := createExampleIngress()
+	updatedExampleIngress := createExampleIngress()
+
+	updatedExampleIngress.Spec.Rules[0].IngressRuleValue.HTTP.Paths[1].Path = "/app2-modified"
+	updatedExampleIngress.Spec.Rules[1].IngressRuleValue.HTTP.Paths[0].Backend.ServiceName = "appsvc1-modified"
+	updatedExampleIngress.Spec.Rules[1].IngressRuleValue.HTTP.Paths[0].Backend.ServicePort = intstr.FromString("9090")
+
+	igHandler.add(&exampleIngress)
+	igHandler.update(&exampleIngress, &updatedExampleIngress)
+
+	returnedKeys := igHandler.Ep.RedisClient.GetDBOneKeyValues()
+
+	expectedKeys := getExpectedKeysForUpdate_ModifyIngress()
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestUpdate_DeletePath(t *testing.T) {
+	igHandler := createExampleIgHandler()
+	exampleIngress := createExampleIngress()
+	updatedExampleIngress := createExampleIngress()
+
+	updatedExampleIngress.Spec.Rules[0].IngressRuleValue.HTTP.Paths = updatedExampleIngress.Spec.Rules[0].IngressRuleValue.HTTP.Paths[:1]
+
+	igHandler.add(&exampleIngress)
+	igHandler.update(&exampleIngress, &updatedExampleIngress)
+
+	returnedKeys := igHandler.Ep.RedisClient.GetDBOneKeyValues()
+	expectedKeys := getExpectedKeysForUpdate_DeleteService()
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestUpdate_ModifySnippet(t *testing.T) {
+	igHandler := createExampleIgHandler()
+	exampleIngress := createExampleIngressWithAnnotation()
+	updatedExampleIngress := createExampleIngressWithAnnotation()
+
+	exampleSnippet := getExampleSnippet()
+	exampleSnippet = exampleSnippet + `
+	ts.debug('Modifications for the purpose of testing')`
+
+	updatedExampleIngress.ObjectMeta.Annotations["ats.ingress.kubernetes.io/server-snippet"] = exampleSnippet
+	updatedExampleIngress.SetResourceVersion("10")
+
+	igHandler.add(&exampleIngress)
+	igHandler.update(&exampleIngress, &updatedExampleIngress)
+
+	returnedKeys := igHandler.Ep.RedisClient.GetDBOneKeyValues()
+	expectedKeys := getExpectedKeysForUpdate_ModifySnippet()
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestUpdate_ModifyTLS(t *testing.T) {
+	igHandler := createExampleIgHandler()
+	exampleIngress := createExampleIngress()
+	updatedExampleIngress := createExampleIngressWithTLS()
+
+	igHandler.add(&exampleIngress)
+	igHandler.update(&exampleIngress, &updatedExampleIngress)
+
+	returnedKeys := igHandler.Ep.RedisClient.GetDBOneKeyValues()
+	expectedKeys := getExpectedKeysForAdd()
+	expectedKeys["https://test.edge.com/app1"] = expectedKeys["http://test.edge.com/app1"]
+	expectedKeys["http://test.edge.com/app1"] = []string{}
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestDelete(t *testing.T) {
+	igHandler := createExampleIgHandler()
+	exampleIngress := createExampleIngress()
+
+	igHandler.delete(&exampleIngress)
+
+	returnedKeys := igHandler.Ep.RedisClient.GetDBOneKeyValues()
+
+	expectedKeys := make(map[string][]string)
+
+	if !util.IsSameMap(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+
+}
+
+func createExampleIngressWithTLS() v1beta1.Ingress {
+	exampleIngress := createExampleIngress()
+
+	exampleIngress.Spec.TLS = []v1beta1.IngressTLS{
+		{
+			Hosts: []string{"test.edge.com"},
+		},
+	}
+
+	return exampleIngress
+}
+
+func createExampleIngressWithAnnotation() v1beta1.Ingress {
+	exampleIngress := createExampleIngress()
+
+	exampleIngress.ObjectMeta.Annotations = make(map[string]string)
+	exampleIngress.ObjectMeta.Annotations["ats.ingress.kubernetes.io/server-snippet"] = getExampleSnippet()
+	exampleIngress.Spec.Rules = exampleIngress.Spec.Rules[1:]
+
+	return exampleIngress
+}
+
+func createExampleIngress() v1beta1.Ingress {
+	exampleIngress := v1beta1.Ingress{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "example-ingress",
+			Namespace: "trafficserver-test",
+		},
+		Spec: v1beta1.IngressSpec{
+			Rules: []v1beta1.IngressRule{
+				{
+					Host: "test.media.com",
+					IngressRuleValue: v1beta1.IngressRuleValue{
+						HTTP: &v1beta1.HTTPIngressRuleValue{
+							Paths: []v1beta1.HTTPIngressPath{
+								{
+									Path: "/app1",
+									Backend: v1beta1.IngressBackend{
+										ServiceName: "appsvc1",
+										ServicePort: intstr.FromString("8080"),
+									},
+								},
+								{
+									Path: "/app2",
+									Backend: v1beta1.IngressBackend{
+										ServiceName: "appsvc2",
+										ServicePort: intstr.FromString("8080"),
+									},
+								},
+							},
+						},
+					},
+				},
+				{
+					Host: "test.edge.com",
+					IngressRuleValue: v1beta1.IngressRuleValue{
+						HTTP: &v1beta1.HTTPIngressRuleValue{
+							Paths: []v1beta1.HTTPIngressPath{
+								{
+									Path: "/app1",
+									Backend: v1beta1.IngressBackend{
+										ServiceName: "appsvc1",
+										ServicePort: intstr.FromString("8080"),
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	return exampleIngress
+}
+
+func createExampleIgHandler() IgHandler {
+	exampleEndpoint := createExampleEndpoint()
+	igHandler := IgHandler{"ingresses", &exampleEndpoint}
+
+	return igHandler
+}
+
+func createExampleEndpoint() ep.Endpoint {
+	rClient, err := redis.InitForTesting()
+	if err != nil {
+		log.Panicln("Redis Error: ", err)
+	}
+
+	namespaceMap := make(map[string]bool)
+	ignoreNamespaceMap := make(map[string]bool)
+
+	nsManager := namespace.NsManager{
+		NamespaceMap:       namespaceMap,
+		IgnoreNamespaceMap: ignoreNamespaceMap,
+	}
+
+	nsManager.Init()
+
+	exampleEndpoint := ep.Endpoint{
+		RedisClient: rClient,
+		ATSManager: &proxy.ATSManager{
+			Namespace:    "default",
+			IngressClass: "",
+		},
+		NsManager: &nsManager,
+	}
+
+	return exampleEndpoint
+}
+
+func getExpectedKeysForUpdate_ModifySnippet() map[string][]string {
+	expectedKeys := getExpectedKeysForAddWithAnnotation()
+
+	updatedSnippet := getExampleSnippet()
+	updatedSnippet = updatedSnippet + `
+	ts.debug('Modifications for the purpose of testing')`
+
+	expectedKeys["$trafficserver-test/example-ingress/10"] = []string{}
+	expectedKeys["$trafficserver-test/example-ingress/10"] = append(expectedKeys["$trafficserver-test/example-ingress/10"], updatedSnippet)
+
+	expectedKeys["http://test.edge.com/app1"] = expectedKeys["http://test.edge.com/app1"][:1]
+	expectedKeys["http://test.edge.com/app1"] = append(expectedKeys["http://test.edge.com/app1"], "$trafficserver-test/example-ingress/10")
+
+	return expectedKeys
+}
+
+func getExpectedKeysForUpdate_ModifyIngress() map[string][]string {
+	expectedKeys := getExpectedKeysForAdd()
+
+	expectedKeys["http://test.media.com/app2"] = []string{}
+
+	expectedKeys["http://test.media.com/app2-modified"] = []string{}
+	expectedKeys["http://test.media.com/app2-modified"] = append(expectedKeys["http://test.media.com/app2"], "trafficserver-test:appsvc2:8080")
+
+	expectedKeys["http://test.edge.com/app1"] = []string{}
+	expectedKeys["http://test.edge.com/app1"] = append(expectedKeys["http://test.edge.com/app1"], "trafficserver-test:appsvc1-modified:9090")
+
+	return expectedKeys
+}
+
+func getExpectedKeysForUpdate_DeleteService() map[string][]string {
+	expectedKeys := getExpectedKeysForAdd()
+
+	expectedKeys["http://test.media.com/app2"] = []string{}
+
+	return expectedKeys
+}
+
+func getExpectedKeysForAdd() map[string][]string {
+	expectedKeys := make(map[string][]string)
+	expectedKeys["http://test.edge.com/app1"] = []string{}
+	expectedKeys["http://test.media.com/app1"] = []string{}
+	expectedKeys["http://test.media.com/app2"] = []string{}
+
+	expectedKeys["http://test.edge.com/app1"] = append(expectedKeys["http://test.edge.com/app1"], "trafficserver-test:appsvc1:8080")
+	expectedKeys["http://test.media.com/app2"] = append(expectedKeys["http://test.media.com/app2"], "trafficserver-test:appsvc2:8080")
+	expectedKeys["http://test.media.com/app1"] = append(expectedKeys["http://test.media.com/app1"], "trafficserver-test:appsvc1:8080")
+
+	return expectedKeys
+}
+
+func getExpectedKeysForAddWithAnnotation() map[string][]string {
+	expectedKeys := getExpectedKeysForAdd()
+
+	delete(expectedKeys, "http://test.media.com/app1")
+	delete(expectedKeys, "http://test.media.com/app2")
+
+	expectedKeys["http://test.edge.com/app1"] = append(expectedKeys["http://test.edge.com/app1"], "$trafficserver-test/example-ingress/")
+
+	exampleSnippet := getExampleSnippet()
+
+	expectedKeys["$trafficserver-test/example-ingress/"] = []string{}
+	expectedKeys["$trafficserver-test/example-ingress/"] = append(expectedKeys["$trafficserver-test/example-ingress/"], exampleSnippet)
+
+	return expectedKeys
+}
+
+func getExampleSnippet() string {
+	return `ts.debug('Debug msg example')
+	ts.error('Error msg example')
+	-- ts.hook(TS_LUA_HOOK_SEND_RESPONSE_HDR, function()
+	--   ts.client_response.header['Location'] = 'https://test.edge.com/app2'
+	-- end)
+	-- ts.http.skip_remapping_set(0)
+	-- ts.http.set_resp(301, 'Redirect')
+	ts.debug('Uncomment the above lines to redirect http request to https')`
+}
diff --git a/watcher/watcher.go b/watcher/watcher.go
index 9ab853d..f09a287 100644
--- a/watcher/watcher.go
+++ b/watcher/watcher.go
@@ -23,8 +23,8 @@ import (
 
 	v1 "k8s.io/api/core/v1"
 
-	k "k8s.io/client-go/kubernetes"
-
+	"k8s.io/client-go/informers"
+	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/tools/cache"
 
 	v1beta1 "k8s.io/api/extensions/v1beta1"
@@ -34,13 +34,14 @@ import (
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 
 	"ingress-ats/endpoint"
+	"ingress-ats/proxy"
 )
 
 // FIXME: watching all namespace does not work...
 
 // Watcher stores all essential information to act on HostGroups
 type Watcher struct {
-	Cs           *k.Clientset
+	Cs           kubernetes.Interface
 	ATSNamespace string
 	Ep           *endpoint.Endpoint
 	StopChan     chan struct{}
@@ -56,27 +57,28 @@ type EventHandler interface {
 
 // Watch creates necessary threads to watch over resources
 func (w *Watcher) Watch() error {
-
 	//================= Watch for Ingress ==================
 	igHandler := IgHandler{"ingresses", w.Ep}
+	igListWatch := cache.NewListWatchFromClient(w.Cs.ExtensionsV1beta1().RESTClient(), igHandler.GetResourceName(), v1.NamespaceAll, fields.Everything())
 	err := w.allNamespacesWatchFor(&igHandler, w.Cs.ExtensionsV1beta1().RESTClient(),
-		fields.Everything(), &v1beta1.Ingress{}, 0)
+		fields.Everything(), &v1beta1.Ingress{}, 0, igListWatch)
 	if err != nil {
 		return err
 	}
 	//================= Watch for Endpoints =================
 	epHandler := EpHandler{"endpoints", w.Ep}
+	epListWatch := cache.NewListWatchFromClient(w.Cs.CoreV1().RESTClient(), epHandler.GetResourceName(), v1.NamespaceAll, fields.Everything())
 	err = w.allNamespacesWatchFor(&epHandler, w.Cs.CoreV1().RESTClient(),
-		fields.Everything(), &v1.Endpoints{}, 0)
+		fields.Everything(), &v1.Endpoints{}, 0, epListWatch)
 	if err != nil {
 		return err
 	}
 	//================= Watch for ConfigMaps =================
 	cmHandler := CMHandler{"configmaps", w.Ep}
 	targetNs := make([]string, 1, 1)
-	targetNs[0] = w.Ep.ATSManager.Namespace
-	err = w.inNamespacesWatchFor(&cmHandler, w.Cs.CoreV1().RESTClient(),
-		targetNs, fields.Everything(), &v1.ConfigMap{}, 0)
+	targetNs[0] = w.Ep.ATSManager.(*proxy.ATSManager).Namespace
+	err = w.inNamespacesWatchForConfigMaps(&cmHandler, w.Cs.CoreV1().RESTClient(),
+		targetNs, fields.Everything(), &v1.ConfigMap{}, 0, w.Cs)
 	if err != nil {
 		return err
 	}
@@ -85,9 +87,8 @@ func (w *Watcher) Watch() error {
 
 func (w *Watcher) allNamespacesWatchFor(h EventHandler, c cache.Getter,
 	fieldSelector fields.Selector, objType pkgruntime.Object,
-	resyncPeriod time.Duration) error {
-	epListWatch := cache.NewListWatchFromClient(c, h.GetResourceName(), v1.NamespaceAll, fieldSelector)
-	sharedInformer := cache.NewSharedInformer(epListWatch, objType, resyncPeriod)
+	resyncPeriod time.Duration, listerWatcher cache.ListerWatcher) error {
+	sharedInformer := cache.NewSharedInformer(listerWatcher, objType, resyncPeriod)
 
 	sharedInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
 		AddFunc:    h.Add,
@@ -107,6 +108,35 @@ func (w *Watcher) allNamespacesWatchFor(h EventHandler, c cache.Getter,
 
 // This is meant to make it easier to add resource watchers on resources that
 // span multiple namespaces
+func (w *Watcher) inNamespacesWatchForConfigMaps(h EventHandler, c cache.Getter,
+	namespaces []string, fieldSelector fields.Selector, objType pkgruntime.Object,
+	resyncPeriod time.Duration, clientset kubernetes.Interface) error {
+	if len(namespaces) == 0 {
+		log.Panic("inNamespacesWatchFor must have at least 1 namespace")
+	}
+	syncFuncs := make([]cache.InformerSynced, len(namespaces))
+	for i, ns := range namespaces {
+		factory := informers.NewSharedInformerFactoryWithOptions(clientset, resyncPeriod, informers.WithNamespace(ns))
+		cmInfo := factory.Core().V1().ConfigMaps().Informer()
+
+		cmInfo.AddEventHandler(cache.ResourceEventHandlerFuncs{
+			AddFunc:    h.Add,
+			UpdateFunc: h.Update,
+			DeleteFunc: h.Delete,
+		})
+
+		go cmInfo.Run(w.StopChan)
+
+		syncFuncs[i] = cmInfo.HasSynced
+	}
+	if !cache.WaitForCacheSync(w.StopChan, syncFuncs...) {
+		s := fmt.Sprintf("Timed out waiting for %s caches to sync", h.GetResourceName())
+		utilruntime.HandleError(fmt.Errorf(s))
+		return errors.New(s)
+	}
+	return nil
+}
+
 func (w *Watcher) inNamespacesWatchFor(h EventHandler, c cache.Getter,
 	namespaces []string, fieldSelector fields.Selector, objType pkgruntime.Object,
 	resyncPeriod time.Duration) error {
diff --git a/watcher/watcher_test.go b/watcher/watcher_test.go
new file mode 100644
index 0000000..e5ed740
--- /dev/null
+++ b/watcher/watcher_test.go
@@ -0,0 +1,419 @@
+/*
+
+   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 watcher
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	v1 "k8s.io/api/core/v1"
+	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/fields"
+	fake "k8s.io/client-go/kubernetes/fake"
+	framework "k8s.io/client-go/tools/cache/testing"
+)
+
+func TestAllNamespacesWatchFor_Add(t *testing.T) {
+	w, fc := getTestWatcher()
+
+	epHandler := EpHandler{"endpoints", w.Ep}
+	err := w.allNamespacesWatchFor(&epHandler, w.Cs.CoreV1().RESTClient(),
+		fields.Everything(), &v1.Endpoints{}, 0, fc)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	fc.Add(&v1.Endpoints{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "testsvc",
+			Namespace: "trafficserver-test-2",
+		},
+		Subsets: []v1.EndpointSubset{
+			{
+				Addresses: []v1.EndpointAddress{
+					{
+						IP: "10.10.1.1",
+					},
+					{
+						IP: "10.10.2.2",
+					},
+				},
+				Ports: []v1.EndpointPort{
+					{
+						Name:     "main",
+						Port:     8080,
+						Protocol: "TCP",
+					},
+				},
+			},
+		},
+	})
+	time.Sleep(100 * time.Millisecond)
+
+	returnedKeys := w.Ep.RedisClient.GetDefaultDBKeyValues()
+	expectedKeys := getExpectedKeysForEndpointAdd()
+
+	if !reflect.DeepEqual(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestAllNamespacesWatchFor_Update(t *testing.T) {
+	w, fc := getTestWatcher()
+
+	epHandler := EpHandler{"endpoints", w.Ep}
+	err := w.allNamespacesWatchFor(&epHandler, w.Cs.CoreV1().RESTClient(),
+		fields.Everything(), &v1.Endpoints{}, 0, fc)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	fc.Add(&v1.Endpoints{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "testsvc",
+			Namespace: "trafficserver-test-2",
+		},
+		Subsets: []v1.EndpointSubset{
+			{
+				Addresses: []v1.EndpointAddress{
+					{
+						IP: "10.10.1.1",
+					},
+					{
+						IP: "10.10.2.2",
+					},
+				},
+				Ports: []v1.EndpointPort{
+					{
+						Name:     "main",
+						Port:     8080,
+						Protocol: "TCP",
+					},
+				},
+			},
+		},
+	})
+	time.Sleep(100 * time.Millisecond)
+
+	fc.Modify(&v1.Endpoints{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "testsvc",
+			Namespace: "trafficserver-test-2",
+		},
+		Subsets: []v1.EndpointSubset{
+			{
+				Addresses: []v1.EndpointAddress{
+					{
+						IP: "10.10.1.1",
+					},
+					{
+						IP: "10.10.3.3",
+					},
+				},
+				Ports: []v1.EndpointPort{
+					{
+						Name:     "main",
+						Port:     8080,
+						Protocol: "TCP",
+					},
+				},
+			},
+		},
+	})
+	time.Sleep(100 * time.Millisecond)
+
+	returnedKeys := w.Ep.RedisClient.GetDefaultDBKeyValues()
+	expectedKeys := getExpectedKeysForEndpointAdd()
+	expectedKeys["trafficserver-test-2:testsvc:8080"][1] = "10.10.3.3#8080#http"
+
+	if !reflect.DeepEqual(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestAllNamespacesWatchFor_Delete(t *testing.T) {
+	w, fc := getTestWatcher()
+
+	epHandler := EpHandler{"endpoints", w.Ep}
+	err := w.allNamespacesWatchFor(&epHandler, w.Cs.CoreV1().RESTClient(),
+		fields.Everything(), &v1.Endpoints{}, 0, fc)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	fc.Add(&v1.Endpoints{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "testsvc",
+			Namespace: "trafficserver-test-2",
+		},
+		Subsets: []v1.EndpointSubset{
+			{
+				Addresses: []v1.EndpointAddress{
+					{
+						IP: "10.10.1.1",
+					},
+					{
+						IP: "10.10.2.2",
+					},
+				},
+				Ports: []v1.EndpointPort{
+					{
+						Name:     "main",
+						Port:     8080,
+						Protocol: "TCP",
+					},
+				},
+			},
+		},
+	})
+	time.Sleep(100 * time.Millisecond)
+
+	fc.Delete(&v1.Endpoints{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "testsvc",
+			Namespace: "trafficserver-test-2",
+		},
+		Subsets: []v1.EndpointSubset{
+			{
+				Addresses: []v1.EndpointAddress{
+					{
+						IP: "10.10.1.1",
+					},
+					{
+						IP: "10.10.3.3",
+					},
+				},
+				Ports: []v1.EndpointPort{
+					{
+						Name:     "main",
+						Port:     8080,
+						Protocol: "TCP",
+					},
+				},
+			},
+		},
+	})
+	time.Sleep(100 * time.Millisecond)
+
+	returnedKeys := w.Ep.RedisClient.GetDefaultDBKeyValues()
+	expectedKeys := make(map[string][]string)
+
+	if !reflect.DeepEqual(returnedKeys, expectedKeys) {
+		t.Errorf("returned \n%v,  but expected \n%v", returnedKeys, expectedKeys)
+	}
+}
+
+func TestInNamespacesWatchFor_Add(t *testing.T) {
+	w, _ := getTestWatcher()
+
+	cmHandler := CMHandler{"configmaps", w.Ep}
+	targetNs := make([]string, 1, 1)
+	targetNs[0] = "trafficserver"
+
+	err := w.inNamespacesWatchForConfigMaps(&cmHandler, w.Cs.CoreV1().RESTClient(),
+		targetNs, fields.Everything(), &v1.ConfigMap{}, 0, w.Cs)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	w.Cs.CoreV1().ConfigMaps("trafficserver").Create(&v1.ConfigMap{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "testsvc",
+			Namespace: "trafficserver",
+		},
+		Data: map[string]string{
+			"proxy.config.output.logfile.rolling_enabled":      "1",
+			"proxy.config.output.logfile.rolling_interval_sec": "4000",
+			"proxy.config.restart.active_client_threshold":     "2",
+		},
+	})
+	time.Sleep(100 * time.Millisecond)
+
+	rEnabled, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_enabled")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(rEnabled, "1") {
+		t.Errorf("returned \n%s,  but expected \n%s", rEnabled, "1")
+	}
+
+	rInterval, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_interval_sec")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(rInterval, "4000") {
+		t.Errorf("returned \n%s,  but expected \n%s", rInterval, "4000")
+	}
+
+	threshold, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.restart.active_client_threshold")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(threshold, "2") {
+		t.Errorf("returned \n%s,  but expected \n%s", threshold, "2")
+	}
+}
+
+func TestInNamespacesWatchFor_Update(t *testing.T) {
+	w, _ := getTestWatcher()
+
+	cmHandler := CMHandler{"configmaps", w.Ep}
+	targetNs := make([]string, 1, 1)
+	targetNs[0] = "trafficserver"
+
+	err := w.inNamespacesWatchForConfigMaps(&cmHandler, w.Cs.CoreV1().RESTClient(),
+		targetNs, fields.Everything(), &v1.ConfigMap{}, 0, w.Cs)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	w.Cs.CoreV1().ConfigMaps("trafficserver").Create(&v1.ConfigMap{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "testsvc",
+			Namespace: "trafficserver",
+		},
+		Data: map[string]string{
+			"proxy.config.output.logfile.rolling_enabled":      "1",
+			"proxy.config.output.logfile.rolling_interval_sec": "4000",
+			"proxy.config.restart.active_client_threshold":     "2",
+		},
+	})
+	time.Sleep(100 * time.Millisecond)
+
+	w.Cs.CoreV1().ConfigMaps("trafficserver").Update(&v1.ConfigMap{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "testsvc",
+			Namespace: "trafficserver",
+		},
+		Data: map[string]string{
+			"proxy.config.output.logfile.rolling_enabled":      "1",
+			"proxy.config.output.logfile.rolling_interval_sec": "3000",
+			"proxy.config.restart.active_client_threshold":     "0",
+		},
+	})
+	time.Sleep(100 * time.Millisecond)
+
+	rEnabled, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_enabled")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(rEnabled, "1") {
+		t.Errorf("returned \n%s,  but expected \n%s", rEnabled, "1")
+	}
+
+	rInterval, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_interval_sec")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(rInterval, "3000") {
+		t.Errorf("returned \n%s,  but expected \n%s", rInterval, "3000")
+	}
+
+	threshold, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.restart.active_client_threshold")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(threshold, "0") {
+		t.Errorf("returned \n%s,  but expected \n%s", threshold, "0")
+	}
+}
+
+func TestInNamespacesWatchFor_ShouldNotAdd(t *testing.T) {
+	w, _ := getTestWatcher()
+
+	cmHandler := CMHandler{"configmaps", w.Ep}
+	targetNs := make([]string, 1, 1)
+	targetNs[0] = "trafficserver"
+
+	err := w.inNamespacesWatchForConfigMaps(&cmHandler, w.Cs.CoreV1().RESTClient(),
+		targetNs, fields.Everything(), &v1.ConfigMap{}, 0, w.Cs)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	w.Cs.CoreV1().ConfigMaps("trafficserver").Create(&v1.ConfigMap{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "testsvc",
+			Namespace: "trafficserver",
+		},
+		Data: map[string]string{
+			"proxy.config.output.logfile.rolling_enabled":      "1",
+			"proxy.config.output.logfile.rolling_interval_sec": "4000",
+			"proxy.config.restart.active_client_threshold":     "2",
+		},
+	})
+	time.Sleep(100 * time.Millisecond)
+
+	w.Cs.CoreV1().ConfigMaps("trafficserver-2").Create(&v1.ConfigMap{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "testsvc-2",
+			Namespace: "trafficserver-2",
+		},
+		Data: map[string]string{
+			"proxy.config.output.logfile.rolling_enabled":      "1",
+			"proxy.config.output.logfile.rolling_interval_sec": "3000",
+			"proxy.config.restart.active_client_threshold":     "4",
+		},
+	})
+	time.Sleep(100 * time.Millisecond)
+
+	rEnabled, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_enabled")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(rEnabled, "1") {
+		t.Errorf("returned \n%s,  but expected \n%s", rEnabled, "1")
+	}
+
+	rInterval, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_interval_sec")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(rInterval, "4000") {
+		t.Errorf("returned \n%s,  but expected \n%s", rInterval, "4000")
+	}
+
+	threshold, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.restart.active_client_threshold")
+
+	if err != nil {
+		t.Error(err)
+	} else if !reflect.DeepEqual(threshold, "2") {
+		t.Errorf("returned \n%s,  but expected \n%s", threshold, "2")
+	}
+}
+
+func getTestWatcher() (Watcher, *framework.FakeControllerSource) {
+	clientset := fake.NewSimpleClientset()
+	fc := framework.NewFakeControllerSource()
+
+	exampleEndpoint := createExampleEndpointWithFakeATS()
+	stopChan := make(chan struct{})
+
+	ingressWatcher := Watcher{
+		Cs:           clientset,
+		ATSNamespace: "trafficserver-test-2",
+		Ep:           &exampleEndpoint,
+		StopChan:     stopChan,
+	}
+
+	return ingressWatcher, fc
+}