trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kic...@apache.org
Subject [trafficserver-ingress-controller] branch master updated: Initial Code Checkin
Date Wed, 29 Apr 2020 22:07:14 GMT
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 44e42b1  Initial Code Checkin
     new e68ee1e  Merge pull request #1 from shukitchan/master
44e42b1 is described below

commit 44e42b135ee0693076ba465f5d9334e00b1e5318
Author: shukitchan <kichan@apache.org>
AuthorDate: Wed Apr 29 15:05:36 2020 -0700

    Initial Code Checkin
---
 .gitignore                               |  13 +
 .vimrc                                   |   5 +
 .vscode/extensions.json                  |  18 ++
 .vscode/settings.json                    |   7 +
 Dockerfile                               | 141 ++++++++++
 README.md                                | 239 +++++++++++++++++
 docs/images/abstract.png                 | Bin 0 -> 215097 bytes
 docs/images/how-it-works.png             | Bin 0 -> 296139 bytes
 endpoint/endpoint.go                     |  48 ++++
 entry.sh                                 |  40 +++
 go.mod                                   |  20 ++
 k8s/apps/app-deployment.yaml             | 135 ++++++++++
 k8s/apps/app-service.yaml                |  89 +++++++
 k8s/backend/node-app-1/.dockerignore     |   3 +
 k8s/backend/node-app-1/Dockerfile        |  30 +++
 k8s/backend/node-app-1/hello.html        |  15 ++
 k8s/backend/node-app-1/package-lock.json | 374 +++++++++++++++++++++++++++
 k8s/backend/node-app-1/package.json      |  14 +
 k8s/backend/node-app-1/server.js         |  36 +++
 k8s/backend/node-app-2/.dockerignore     |   3 +
 k8s/backend/node-app-2/Dockerfile        |  30 +++
 k8s/backend/node-app-2/hello.html        |  15 ++
 k8s/backend/node-app-2/package-lock.json | 374 +++++++++++++++++++++++++++
 k8s/backend/node-app-2/package.json      |  14 +
 k8s/backend/node-app-2/server.js         |  36 +++
 k8s/configmaps/ats-configmap.yaml        |  33 +++
 k8s/ingresses/ats-ingress-2.yaml         |  41 +++
 k8s/ingresses/ats-ingress-2s.yaml        |  40 +++
 k8s/ingresses/ats-ingress.yaml           |  50 ++++
 k8s/traffic-server/ats-deployment.yaml   | 103 ++++++++
 k8s/traffic-server/ats-rbac.yaml         |  60 +++++
 logging.config                           |  61 +++++
 logrotate.ingress                        |  23 ++
 main/main.go                             | 174 +++++++++++++
 namespace/namespace.go                   |  53 ++++
 plugin.config                            |  17 ++
 pluginats/connect_redis.lua              | 127 +++++++++
 proxy/ats.go                             |  52 ++++
 records.config                           | 201 +++++++++++++++
 redis.conf                               |  20 ++
 redis/redis.go                           | 191 ++++++++++++++
 tls-config.sh                            |  40 +++
 tls-reload.sh                            |  48 ++++
 types/metadataManagers.go                | 292 +++++++++++++++++++++
 types/types.go                           | 424 +++++++++++++++++++++++++++++++
 util/util.go                             | 153 +++++++++++
 watcher/handlerConfigmap.go              |  68 +++++
 watcher/handlerEndpoint.go               | 144 +++++++++++
 watcher/handlerIngress.go                | 289 +++++++++++++++++++++
 watcher/watcher.go                       | 137 ++++++++++
 50 files changed, 4540 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..26f910c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+# go dependency files
+vendor/
+
+# go mod
+go.sum
+!go.mod
+
+# builds
+ingress*ats
+
+# untracking
+trash/
+other/
diff --git a/.vimrc b/.vimrc
new file mode 100644
index 0000000..515ebef
--- /dev/null
+++ b/.vimrc
@@ -0,0 +1,5 @@
+" Basic whitespace rules.
+set tabstop=4
+set softtabstop=4
+set shiftwidth=4
+set expandtab
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..22a7434
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,18 @@
+{
+	// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
+	// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
+
+	// List of extensions which should be recommended for users of this workspace.
+	"recommendations": [
+		"ms-vscode.go", // Go-lang language support
+		"redhat.vscode-yaml", // docker + k8s code hints and code snippets
+		"keyring.lua", // lua language support
+		"ms-kubernetes-tools.vscode-kubernetes-tools", // managing k8s
+		"ms-azuretools.vscode-docker", // managing docker
+		"wayou.vscode-todo-highlight" // highlighting TODOs and FIXMEs
+	],
+	// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
+	"unwantedRecommendations": [
+		
+	]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..ef949af
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+    // Editor
+    "editor.tabSize": 4,
+    "editor.detectIndentation": true,
+    "editor.autoIndent": true,
+    "editor.insertSpaces": true
+}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..8d863ed
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,141 @@
+# 
+# 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.
+#
+
+FROM alpine:3.11 as builder 
+
+RUN apk add --no-cache --virtual .tools \
+  bzip2 curl git automake libtool autoconf make sed file perl openrc openssl
+
+# ATS
+RUN apk add --no-cache --virtual .ats-build-deps \
+  build-base openssl-dev tcl-dev pcre-dev zlib-dev \
+  libexecinfo-dev linux-headers libunwind-dev \
+  brotli-dev jansson-dev luajit-dev readline-dev geoip-dev 
+
+RUN curl -L https://www-us.apache.org/dist/trafficserver/trafficserver-8.0.7.tar.bz2 | bzip2 -dc | tar xf - \
+  && cd trafficserver-8.0.7/ \
+  && autoreconf -if \
+  && ./configure --enable-debug=yes \
+  && make \
+  && make install
+
+COPY ["./plugin.config", "/usr/local/etc/trafficserver/plugin.config"]
+COPY ["./records.config", "/usr/local/etc/trafficserver/records.config"]
+COPY ["./logging.config", "/usr/local/etc/trafficserver/logging.config"]
+# enable traffic.out for alpine/gentoo
+RUN sed -i "s/TM_DAEMON_ARGS=\"\"/TM_DAEMON_ARGS=\" --bind_stdout \/usr\/local\/var\/log\/trafficserver\/traffic.out --bind_stderr \/usr\/local\/var\/log\/trafficserver\/traffic.out \"/" /usr/local/bin/trafficserver
+RUN sed -i "s/TS_DAEMON_ARGS=\"\"/TS_DAEMON_ARGS=\" --bind_stdout \/usr\/local\/var\/log\/trafficserver\/traffic.out --bind_stderr \/usr\/local\/var\/log\/trafficserver\/traffic.out \"/" /usr/local/bin/trafficserver
+
+# Installing lua 5.1.4 
+RUN curl -R -O http://www.lua.org/ftp/lua-5.1.4.tar.gz \
+    && tar zxf lua-5.1.4.tar.gz \
+    && cd lua-5.1.4 \
+    && make linux test \
+    && make linux install
+
+# luasocket
+RUN wget https://github.com/diegonehab/luasocket/archive/v3.0-rc1.tar.gz \
+  && tar zxf v3.0-rc1.tar.gz \
+  && cd luasocket-3.0-rc1 \
+  && sed -i "s/LDFLAGS_linux=-O -shared -fpic -o/LDFLAGS_linux=-O -shared -fpic -L\/usr\/lib -lluajit-5.1 -o/" src/makefile \
+  && ln -sf /usr/lib/libluajit-5.1.so.2.1.0 /usr/lib/libluajit-5.1.so \
+  && make \
+  && make install-unix
+
+# redis.lua
+RUN wget https://github.com/nrk/redis-lua/archive/v2.0.4.tar.gz \
+  && tar zxf v2.0.4.tar.gz \
+  && cp redis-lua-2.0.4/src/redis.lua /usr/local/share/lua/5.1/redis.lua
+
+# ingress-ats
+RUN apk add --no-cache --virtual .ingress-build-deps \
+  bash gcc musl-dev openssl go
+
+# Installing Golang https://github.com/CentOS/CentOS-Dockerfiles/blob/master/golang/centos7/Dockerfile
+RUN wget https://dl.google.com/go/go1.12.8.src.tar.gz \
+    && tar -C /usr/local -xzf go1.12.8.src.tar.gz && cd /usr/local/go/src/ && ./make.bash
+ENV PATH=${PATH}:/usr/local/go/bin
+ENV GOPATH="/usr/local/go/bin"
+
+# ----------------------- Copy over Project Code to Go path ------------------------
+RUN mkdir -p /usr/local/go/bin/src/ingress-ats 
+
+COPY ["./main/", "$GOPATH/src/ingress-ats/main"]
+COPY ["./proxy/", "$GOPATH/src/ingress-ats/proxy"]
+COPY ["./namespace/", "$GOPATH/src/ingress-ats/namespace"]
+COPY ["./endpoint/", "$GOPATH/src/ingress-ats/endpoint"]
+COPY ["./types/", "$GOPATH/src/ingress-ats/types"]
+COPY ["./util/", "$GOPATH/src/ingress-ats/util"]
+COPY ["./watcher/", "$GOPATH/src/ingress-ats/watcher"]
+COPY ["./pluginats/", "$GOPATH/src/ingress-ats/pluginats"]
+COPY ["./redis/", "$GOPATH/src/ingress-ats/redis"]
+COPY ["./go.mod", "$GOPATH/src/ingress-ats/go.mod"]
+
+# Building Project Main
+WORKDIR /usr/local/go/bin/src/ingress-ats
+ENV GO111MODULE=on
+RUN go build -o ingress_ats main/main.go 
+
+# redis conf 
+COPY ["./redis.conf", "/usr/local/etc/redis.conf"]
+
+# entry.sh + other scripts
+COPY ["./tls-config.sh", "/usr/local/bin/tls-config.sh"]
+COPY ["./tls-reload.sh", "/usr/local/bin/tls-reload.sh"]
+COPY ["./entry.sh", "/usr/local/bin/entry.sh"]
+WORKDIR /usr/local/bin/
+RUN chmod 755 tls-config.sh
+RUN chmod 755 tls-reload.sh
+RUN chmod 755 entry.sh
+
+FROM alpine:3.11
+
+COPY --from=builder /usr/local /usr/local
+
+# essential library  
+RUN apk add -U \
+    bash \
+    build-base \
+    curl ca-certificates \
+    pcre \
+    zlib \
+    openssl \
+    brotli \
+    jansson \
+    luajit \
+    libunwind \ 
+    readline \
+    geoip \
+    libexecinfo \
+    redis \
+    tcl \
+    openrc \
+    inotify-tools \
+    cpulimit \
+    logrotate
+
+# redis
+RUN mkdir -p /var/run/redis/ \
+  && touch /var/run/redis/redis.sock \
+  && mkdir -p /var/log/redis
+
+# symlink for luajit
+RUN ln -sf /usr/lib/libluajit-5.1.so.2.1.0 /usr/lib/libluajit-5.1.so
+
+COPY ["./logrotate.ingress", "/etc/logrotate.d/ingress"]
+
+ENTRYPOINT ["/usr/local/bin/entry.sh"]
diff --git a/README.md b/README.md
index 5a73d7b..bca2ad2 100644
--- a/README.md
+++ b/README.md
@@ -20,5 +20,244 @@
 ATS Kubernetes Ingress Controller
 =================================
 
+## Prerequisites
+It is assumed that you understand conceptually, [docker containers](https://www.docker.com/resources/what-container), [kubernetes](https://kubernetes.io/), and [proxy servers](https://en.wikipedia.org/wiki/Proxy_server)
+
+*Throughout ALL documentations, this project is referred to as "**ingress controller**" or "**the controller**"
+
+## Contents
+- [ATS Kubernetes Ingress Controller](#ats-kubernetes-ingress-controller)
+  - [Prerequisites](#prerequisites)
+  - [Contents](#contents)
+  - [Abstract](#abstract)
+  - [What is Ingress Controller?](#what-is-ingress-controller)
+  - [Versions](#versions)
+  - [How to use](#how-to-use)
+    - [Required Software](#required-software)
+    - [Download project](#download-project)
+    - [Example Walkthrough](#example-walkthrough)
+      - [Proxy](#proxy)
+      - [ConfigMap](#configmap)
+      - [Snippet](#snippet)
+      - [Ingress Class](#ingressclass)
+  - [Development](#development)
+    - [Develop with Go-Lang in Linux](#develop-with-go-lang-in-linux)
+    - [Compilation](#compilation)
+    - [Text-Editor](#text-editor)
+  - [Documentation](#documentation)
+
 ## Abstract
 [Apache Traffic Server (ATS)](https://trafficserver.apache.org/) is a high performance, open-source, caching proxy server that is scalable and configurable. This project integrates ATS as the ingress resource to a [Kubernetes(K8s)](https://kubernetes.io/) cluster, then acts as the ingress resource's custom controller. 
+
+
+![Abstract](docs/images/abstract.png)
+
+
+From high-level, the ingress controller talks to K8s' API and sets up `watchers` on specific resources that are interesting to ATS. Then, the controller _controls_ ATS by either (1) relay the information from K8s API to ATS, or (2) configure ATS directly.
+
+
+![How](docs/images/how-it-works.png)
+
+
+
+## What is Ingress Controller?
+As defined by [kubernetes/ingress-nginx](https://github.com/kubernetes/ingress-nginx#what-is-an-ingress-controller):
+>An Ingress Controller is a daemon, deployed as a Kubernetes Pod, that watches the apiserver's `/ingresses` endpoint for updates to the [Ingress resource](https://kubernetes.io/docs/concepts/services-networking/ingress/). Its job is to satisfy requests for Ingresses.
+
+## Versions 
+- Alpine 3.11
+- Apache Traffic Server 8.0.6
+- LuaJIT 2.0.4
+- Lua 5.1.4
+- Go 1.12.8
+- Other Packages
+  - luasocket 3.0rc1
+  - redis-lua 2.0.4
+
+## How to use
+
+### Required Software
+- Docker
+- Kubernetes
+
+To install Docker, visit its [official page](https://docs.docker.com/) and install the correct version for your system.
+
+The easiest way to get Kubernetes on a Mac is through Docker. Launch Docker on your machine and go to `Preferences -> Kubernetes`, Click `Enable Kubernetes` then `Apply`. Kubernetes will be enabled and starting.
+
+### Download project 
+If you are cloning this project for development, visit [Setting up Go-Lang](#setting-up-go-lang) for detailed guide on how to develop projects in Go. 
+
+For other purposes, you can use `git clone` or directly download repository to your computer.
+
+### Example Walkthrough
+Once you have cloned the project repo and started Docker and Kubernetes, in the terminal:
+1. `$ cd ingress-ats`
+2. `$ docker build -t ats_alpine .` 
+     - wait for Docker finish building the image
+3. `$ docker build -t node-app-1 k8s/backend/node-app-1/`    
+     - wait for Docker finish building the image
+4. `$ docker build -t node-app-2 k8s/backend/node-app-2/`
+     - wait for Docker finish building the image
+
+- At this point, we have created necessary images for our example. Let's talk about what each step does:
+  - Step 2 builds an image to create a Docker container that will contain the Apache Traffic Server (ATS) itself, the kubernetes ingress controller, along with other software required for the controller to do its job.
+  - Steps 3 and 4 builds 2 images that will serve as backends to [kubernetes services](https://kubernetes.io/docs/concepts/services-networking/service/) which we will shortly create
+
+5. `$ kubectl create namespace trafficserver-test`
+    - Create a namespace for ATS pod
+6. `$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=atssvc/O=atssvc"`
+    - Create a self-signed certificate
+7. `$ kubectl create secret tls tls-secret --key tls.key --cert tls.crt -n trafficserver-test --dry-run -o yaml | kubectl apply -f -`
+    - Create a secret in the namespace just created
+5. `$ kubectl apply -f k8s/traffic-server/`
+    -  will define a new [kubernetes namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) named `trafficserver-test` and deploy a single ATS pod to said namespace. The ATS pod is also where the ingress controller lives. Additionally, this will expose your local machine's port `30000` to the outside world.
+
+#### Proxy
+
+The following steps can be executed in any order, thus list numbers are not used.
+
+- `$ kubectl apply -f k8s/apps/`
+  - creates namespaces `trafficserver-test-2` and `trafficserver-test-3` if not already exist
+  - creates kubernetes services and [deployments](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) for `appsvc1` and `appsvc2`
+  - deploy 2 of each `appsvc1`, and `appsvc2` pods in `trafficserver-test-2`, totally 4 pods in said namespace.
+  - similarly, deploy 2 of each `appsvc1`, and `appsvc2` pods in `trafficserver-test-3`, totally 4 pods in this namespace. We now have 8 pods in total for the 2 services we have created and deployed in the 2 namespaces.
+  - in addition to the ATS pod, we have created and deployed 9 pods.
+
+- `$ kubectl apply -f k8s/ingresses/`
+  - creates namespaces `trafficserver-test-2` and `trafficserver-test-3` if not already exist
+  - defines an ingress resource in both `trafficserver-test-2` and `trafficserver-test-3`
+  - the ingress resource in `trafficserver-test-2` defines domain name `test.media.com` with `/app1` and `/app2` as its paths
+  - both ingress resources define domain name `test.edge.com`; however, `test.edge.com/app1` is only defined in `trafficserver-test-2` and `test.edge.com/app2` is only defined in `trafficserver-test-3`
+  - Addtionally, an ingress resources defines HTTPS access for `test.edge.com/app2` in namespace `trafficserver-test-3`
+
+When both steps _above_ have executed at least once, ATS proxying will have started to work. 
+
+In kubernetes, ingress resources are necessary to enable proxying since it is where domain names are defined. However, given _only_ domain names, ATS cannot proxy requests when it doesn't have backend(s) to handle requests. Thus, only when both service pods and ingress are defined can ATS start proxying. To see proxy in action, we can use [curl](https://linux.die.net/man/1/curl) to "fake" external requests:
+
+1. `$ curl -vH "HOST:test.media.com" "localhost:30000/app1"`
+2. `$ curl -vH "HOST:test.media.com" "localhost:30000/app2"`
+3. `$ curl -vH "HOST:test.edge.com" "localhost:30000/app1"`
+4. `$ curl -vH "HOST:test.edge.com" "localhost:30000/app2"`
+5. `$ curl -vH "HOST:test.edge.com" -k "https://localhost:30043/app2"`
+
+With above curl commands, outputs from number 1 and 3 should be the same; outputs from number 2 and 4 should be same. The corresponding pairs are the same because all `/app1` use the same backend service _image_, and the same goes for `/app2`. Number 5 illustrates the https version for number 4 and the result is similar. 
+
+Expected received packet from `/app1` resembles:
+```html
+< HTTP/1.1 200 OK
+< X-Powered-By: Express
+< Accept-Ranges: bytes
+< Cache-Control: public, max-age=0
+< Last-Modified: Tue, 06 Aug 2019 16:31:53 GMT
+< ETag: W/"be-16c67c5d0a8"
+< Content-Type: text/html; charset=UTF-8
+< Content-Length: 190
+< Date: Mon, 19 Aug 2019 18:39:14 GMT
+< Age: 1
+< Connection: keep-alive
+< Server: ATS/7.1.6
+< 
+<!DOCTYPE html>
+<HTML>
+
+<HEAD>
+    <TITLE>
+        Hello from app1
+    </TITLE>
+</HEAD>
+
+<BODY>
+    <H1>Hi</H1>
+    <P>This is very minimal "hello world" HTML document.</P>
+</BODY>
+
+</HTML>
+* Connection #0 to host localhost left intact
+```
+
+Expected received packet from `/app2` resembles:
+```html
+< HTTP/1.1 200 OK
+< X-Powered-By: Express
+< Accept-Ranges: bytes
+< Cache-Control: public, max-age=0
+< Last-Modified: Fri, 14 Jun 2019 18:18:51 GMT
+< ETag: W/"bc-16b5736b2f8"
+< Content-Type: text/html; charset=UTF-8
+< Content-Length: 188
+< Date: Mon, 19 Aug 2019 18:39:10 GMT
+< Age: 0
+< Connection: keep-alive
+< Server: ATS/7.1.6
+< 
+<!DOCTYPE html>
+<HTML>
+
+<HEAD>
+    <TITLE>
+        A Small Hello
+    </TITLE>
+</HEAD>
+
+<BODY>
+    <H1>Hi</H1>
+    <P>This is very minimal "hello world" HTML document.</P>
+</BODY>
+
+</HTML>
+* Connection #0 to host localhost left intact
+```
+
+The curl commands demonstrate that, with the help of the ingress controller, ATS can not only resolve domain names while routing requests to various namespaces based on path, but also, is capable of handling the case where domain's paths existing across different namespaces. 
+
+Of course there are checks in place by the ingress controller so that any path corresponding to a domain name can only be defined in one namespace, and domain names are resolved across all namespace. 
+
+#### ConfigMap
+
+Below is an example of configuring Apache Traffic Server [_reloadable_ configurations](https://docs.trafficserver.apache.org/en/8.0.x/admin-guide/files/records.config.en.html#reloadable) using [kubernetes configmap](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/) resource:
+
+- `$ kubectl apply -f k8s/configmaps/`
+  - create a ConfigMap resource in `trafficserver-test` if not already exist
+  - configure 3 _reloadable_ ATS configurations:
+    1. `proxy.config.output.logfile.rolling_enabled: "1"`
+    2. `proxy.config.output.logfile.rolling_interval_sec: "3000"`
+    3. `proxy.config.restart.active_client_threshold: "0"`
+  - feel free to add other reloadable configurations, and/or change the above 3 to other valid values. Checks are in place so that mistakes are tolerated.
+
+#### Snippet
+
+You can attach [ATS lua script](https://docs.trafficserver.apache.org/en/8.0.x/admin-guide/plugins/lua.en.html) to an ingress object and ATS will execute it for requests matching the routing rules defined in the ingress object. See an example in k8s/ingresses/ats-ingress-2.yaml 
+
+#### Ingress Class
+
+You can provide an environment variable called `INGRESS_CLASS` in the deployment to specify the ingress class. Only ingress object with annotation `kubernetes.io/ingress.class` with value equal to the environment variable value will be used by ATS for routing
+
+## Development
+
+### Develop with Go-Lang in Linux
+1. Get Go-lang 1.12 from [official site](https://golang.org/dl/)
+2. Add `go` command to your PATH: `export PATH=$PATH:/usr/local/go/bin`
+3. Define GOPATH: `export GOPATH=$(go env GOPATH)`
+4. Add Go workspace to your PATH: `export PATH=$PATH:$(go env GOPATH)/bin`
+5. Define Go import Paths
+   - Go's import path is different from other languages in that all import paths are _absolute paths_. Due to this reason, it is important to set up your project paths correctly
+   - define the base path: `mkdir -p $GOPATH/src/github.com/<your user name>/`
+6. Clone the project:
+   - `cd $GOPATH/src/github.com/<your user name>/`
+   - `git clone <project>`
+7. As of Go 1.12 in order to have `go.mod` within Go paths, you must export: `export GO111MODULE=on` to be able to compile locally. 
+
+*Above steps are a very short summary of [Getting Started](https://golang.org/doc/install) and [How to Write Go Code](https://golang.org/doc/code.html) from official Go-lang documentation. For more detailed info and/or assistance, it is recommended to checkout these 2 links first.
+
+### Compilation
+To compile, while in `ingress-ats/` directory: `go build -o ingress_ats main/main.go`
+
+### Text-Editor
+The repository comes with basic support for both [vscode](https://code.visualstudio.com/) and `vim`. 
+
+If you're using `vscode`:
+- `.vscode/settings.json` contains some basic settings for whitespaces and tabs
+- `.vscode/extensions.json` contains a few recommended extensions for this project. It is highly recommended to install the [Go extension](https://github.com/Microsoft/vscode-go) since it contains the code lint this project used during development.
+
+If you're using `vim`, a `vimrc` file with basic whitespace and tab configurations is also provided
+
diff --git a/docs/images/abstract.png b/docs/images/abstract.png
new file mode 100644
index 0000000..f5748b5
Binary files /dev/null and b/docs/images/abstract.png differ
diff --git a/docs/images/how-it-works.png b/docs/images/how-it-works.png
new file mode 100644
index 0000000..8b6f550
Binary files /dev/null and b/docs/images/how-it-works.png differ
diff --git a/endpoint/endpoint.go b/endpoint/endpoint.go
new file mode 100644
index 0000000..516fa57
--- /dev/null
+++ b/endpoint/endpoint.go
@@ -0,0 +1,48 @@
+/*
+
+   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 endpoint
+
+import (
+	//	"fmt"
+	//	"log"
+
+	//	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
+
+	//	v1beta1 "k8s.io/api/extensions/v1beta1"
+	//	extensionsV1beta1 "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"
+
+	//	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"ingress-ats/namespace"
+	"ingress-ats/proxy"
+	"ingress-ats/redis"
+	//	t "ingress-ats/types"
+	//	"ingress-ats/util"
+)
+
+const (
+	// UpdateRedis for readability
+	UpdateRedis bool = true
+	// UpdateATS for readability
+	UpdateATS bool = true
+)
+
+// Endpoint stores all essential information to act on HostGroups
+type Endpoint struct {
+	RedisClient *redis.Client
+	ATSManager  *proxy.ATSManager
+	NsManager   *namespace.NsManager
+}
diff --git a/entry.sh b/entry.sh
new file mode 100755
index 0000000..77e85da
--- /dev/null
+++ b/entry.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+
+set +x
+
+# start basic service
+syslogd
+crond
+
+# TLS auto reload script
+/usr/local/bin/tls-reload.sh >> /usr/local/var/log/ingress_ats.err &
+
+# generate TLS cert config file for ats 
+/usr/local/bin/tls-config.sh 
+
+# start redis
+redis-server /usr/local/etc/redis.conf 
+
+# start ats
+chown -R nobody:nobody /usr/local/etc/trafficserver
+DISTRIB_ID=gentoo /usr/local/bin/trafficserver start
+
+sleep 20 
+/usr/local/go/bin/src/ingress-ats/ingress_ats -atsIngressClass="$INGRESS_CLASS" -atsNamespace="$POD_NAMESPACE" -useInClusterConfig=T 2>>/usr/local/var/log/ingress_ats.err
+
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..3dc085c
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,20 @@
+module ingress-ats
+
+go 1.12
+
+require (
+	github.com/deckarep/golang-set v1.7.1
+	github.com/go-redis/redis v6.15.2+incompatible
+	github.com/golang/protobuf v1.3.1 // indirect
+	github.com/googleapis/gnostic v0.3.0 // indirect
+	github.com/imdario/mergo v0.3.7 // indirect
+	golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 // indirect
+	golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
+	golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect
+	golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
+	k8s.io/api v0.0.0-20190626000116-b178a738ed00
+	k8s.io/apimachinery v0.0.0-20190624085041-961b39a1baa0
+	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
+)
diff --git a/k8s/apps/app-deployment.yaml b/k8s/apps/app-deployment.yaml
new file mode 100644
index 0000000..ea7b433
--- /dev/null
+++ b/k8s/apps/app-deployment.yaml
@@ -0,0 +1,135 @@
+#  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.
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: trafficserver-test-2
+
+---
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: trafficserver-test-3
+
+---
+
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  name: app1
+  namespace: trafficserver-test-2
+spec:
+  replicas: 2
+  template:
+    metadata:
+      labels:
+        app: app1
+    spec:
+      containers:
+      - name: app1
+        image: node-app-1:latest
+        imagePullPolicy: Never
+        env:
+        - name: AUTHOR
+          value: app1
+        ports:
+        - containerPort: 8080
+        resources:
+          limits:
+            memory: '150Mi'
+            cpu: 50m
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  name: app2
+  namespace: trafficserver-test-2
+spec:
+  replicas: 2
+  template:
+    metadata:
+      labels:
+        app: app2
+    spec:
+      containers:
+      - name: app2
+        image: node-app-2:latest
+        imagePullPolicy: Never
+        env:
+        - name: AUTHOR
+          value: app2
+        ports:
+        - containerPort: 8080
+        resources:
+          limits:
+            memory: '150Mi'
+            cpu: 50m
+
+---
+
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  name: app1
+  namespace: trafficserver-test-3
+spec:
+  replicas: 2
+  template:
+    metadata:
+      labels:
+        app: app1
+    spec:
+      containers:
+      - name: app1
+        image: node-app-1:latest
+        imagePullPolicy: Never
+        env:
+        - name: AUTHOR
+          value: app1
+        ports:
+        - containerPort: 8080
+        resources:
+          limits:
+            memory: '150Mi'
+            cpu: 50m
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  name: app2
+  namespace: trafficserver-test-3
+spec:
+  replicas: 2
+  template:
+    metadata:
+      labels:
+        app: app2
+    spec:
+      containers:
+      - name: app2
+        image: node-app-2:latest
+        imagePullPolicy: Never
+        env:
+        - name: AUTHOR
+          value: app2
+        ports:
+        - containerPort: 8080
+        resources:
+          limits:
+            memory: '150Mi'
+            cpu: 50m
diff --git a/k8s/apps/app-service.yaml b/k8s/apps/app-service.yaml
new file mode 100644
index 0000000..260c8cf
--- /dev/null
+++ b/k8s/apps/app-service.yaml
@@ -0,0 +1,89 @@
+#  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.
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: trafficserver-test-2
+
+---
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: trafficserver-test-3
+
+---
+
+apiVersion: v1
+kind: Service
+metadata:
+  name: appsvc1
+  namespace: trafficserver-test-2
+spec:
+  ports:
+  - port: 8080
+    protocol: TCP
+    targetPort: 8080
+  selector:
+    app: app1
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: appsvc2
+  namespace: trafficserver-test-2
+spec:
+  ports:
+  - port: 8080
+    name: "main"
+    protocol: TCP
+    targetPort: 8080
+
+  selector:
+    app: app2
+
+---
+
+apiVersion: v1
+kind: Service
+metadata:
+  name: appsvc1
+  namespace: trafficserver-test-3
+spec:
+  ports:
+  - port: 8080
+    protocol: TCP
+    targetPort: 8080
+  selector:
+    app: app1
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: appsvc2
+  namespace: trafficserver-test-3
+spec:
+  ports:
+  - port: 8080
+    name: "main"
+    protocol: TCP
+    targetPort: 8080
+
+  selector:
+    app: app2
+
+  
diff --git a/k8s/backend/node-app-1/.dockerignore b/k8s/backend/node-app-1/.dockerignore
new file mode 100644
index 0000000..29d6828
--- /dev/null
+++ b/k8s/backend/node-app-1/.dockerignore
@@ -0,0 +1,3 @@
+node_modules
+npm-debug.log
+
diff --git a/k8s/backend/node-app-1/Dockerfile b/k8s/backend/node-app-1/Dockerfile
new file mode 100644
index 0000000..5e3ab97
--- /dev/null
+++ b/k8s/backend/node-app-1/Dockerfile
@@ -0,0 +1,30 @@
+#  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.
+
+FROM node:8
+
+WORKDIR /usr/src/app
+
+COPY package*.json ./
+
+RUN npm install
+
+COPY . . 
+
+EXPOSE 8080
+
+CMD ["npm", "start"]
+
diff --git a/k8s/backend/node-app-1/hello.html b/k8s/backend/node-app-1/hello.html
new file mode 100644
index 0000000..e90e8a8
--- /dev/null
+++ b/k8s/backend/node-app-1/hello.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<HTML>
+
+<HEAD>
+    <TITLE>
+        Hello from app1
+    </TITLE>
+</HEAD>
+
+<BODY>
+    <H1>Hi</H1>
+    <P>This is very minimal "hello world" HTML document.</P>
+</BODY>
+
+</HTML>
diff --git a/k8s/backend/node-app-1/package-lock.json b/k8s/backend/node-app-1/package-lock.json
new file mode 100644
index 0000000..785a743
--- /dev/null
+++ b/k8s/backend/node-app-1/package-lock.json
@@ -0,0 +1,374 @@
+{
+  "name": "dockernode",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "accepts": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+      "requires": {
+        "mime-types": "~2.1.24",
+        "negotiator": "0.6.2"
+      }
+    },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+    },
+    "body-parser": {
+      "version": "1.19.0",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+      "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+      "requires": {
+        "bytes": "3.1.0",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "1.7.2",
+        "iconv-lite": "0.4.24",
+        "on-finished": "~2.3.0",
+        "qs": "6.7.0",
+        "raw-body": "2.4.0",
+        "type-is": "~1.6.17"
+      }
+    },
+    "bytes": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+      "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
+    },
+    "content-disposition": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+      "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+      "requires": {
+        "safe-buffer": "5.1.2"
+      }
+    },
+    "content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+    },
+    "cookie": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+      "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+    },
+    "destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+    },
+    "express": {
+      "version": "4.17.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+      "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+      "requires": {
+        "accepts": "~1.3.7",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.19.0",
+        "content-disposition": "0.5.3",
+        "content-type": "~1.0.4",
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.1.2",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.5",
+        "qs": "6.7.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.1.2",
+        "send": "0.17.1",
+        "serve-static": "1.14.1",
+        "setprototypeof": "1.1.1",
+        "statuses": "~1.5.0",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      }
+    },
+    "finalhandler": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "statuses": "~1.5.0",
+        "unpipe": "~1.0.0"
+      }
+    },
+    "forwarded": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+    },
+    "http-errors": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+      "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+      "requires": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.3",
+        "setprototypeof": "1.1.1",
+        "statuses": ">= 1.5.0 < 2",
+        "toidentifier": "1.0.0"
+      }
+    },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+    },
+    "ipaddr.js": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
+      "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+    },
+    "mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+    },
+    "mime-db": {
+      "version": "1.40.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+      "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
+    },
+    "mime-types": {
+      "version": "2.1.24",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+      "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+      "requires": {
+        "mime-db": "1.40.0"
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "negotiator": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
+    "proxy-addr": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
+      "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
+      "requires": {
+        "forwarded": "~0.1.2",
+        "ipaddr.js": "1.9.0"
+      }
+    },
+    "qs": {
+      "version": "6.7.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+      "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
+    },
+    "range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+    },
+    "raw-body": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+      "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+      "requires": {
+        "bytes": "3.1.0",
+        "http-errors": "1.7.2",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "send": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+      "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "destroy": "~1.0.4",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "~1.7.2",
+        "mime": "1.6.0",
+        "ms": "2.1.1",
+        "on-finished": "~2.3.0",
+        "range-parser": "~1.2.1",
+        "statuses": "~1.5.0"
+      },
+      "dependencies": {
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+        }
+      }
+    },
+    "serve-static": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+      "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.17.1"
+      }
+    },
+    "setprototypeof": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+      "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
+    },
+    "statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
+    },
+    "toidentifier": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+      "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
+    },
+    "type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      }
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+    }
+  }
+}
diff --git a/k8s/backend/node-app-1/package.json b/k8s/backend/node-app-1/package.json
new file mode 100644
index 0000000..e5202ed
--- /dev/null
+++ b/k8s/backend/node-app-1/package.json
@@ -0,0 +1,14 @@
+{
+  "name": "dockernode",
+  "version": "1.0.0",
+  "description": "docker-ize a very simple express.js app",
+  "main": "server.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "max yao",
+  "license": "ISC",
+  "dependencies": {
+    "express": "^4.17.1"
+  }
+}
diff --git a/k8s/backend/node-app-1/server.js b/k8s/backend/node-app-1/server.js
new file mode 100644
index 0000000..61b8288
--- /dev/null
+++ b/k8s/backend/node-app-1/server.js
@@ -0,0 +1,36 @@
+// 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.
+
+'use strict';
+
+const express = require('express');
+
+// Constants
+const PORT = 8080;
+const HOST = '0.0.0.0';
+
+// App
+const app = express();
+app.get('/', (req, res) => {
+  res.send('Hello world\n');
+});
+
+app.get('/test', (req, res) => {
+  res.sendFile('hello.html', {root: __dirname });
+})
+
+app.get('/app1', (req, res) => {
+  res.sendFile('hello.html', {root: __dirname });
+})
+
+app.listen(PORT, HOST);
+console.log(`Running on http://${HOST}:${PORT}`);
diff --git a/k8s/backend/node-app-2/.dockerignore b/k8s/backend/node-app-2/.dockerignore
new file mode 100644
index 0000000..29d6828
--- /dev/null
+++ b/k8s/backend/node-app-2/.dockerignore
@@ -0,0 +1,3 @@
+node_modules
+npm-debug.log
+
diff --git a/k8s/backend/node-app-2/Dockerfile b/k8s/backend/node-app-2/Dockerfile
new file mode 100644
index 0000000..5e3ab97
--- /dev/null
+++ b/k8s/backend/node-app-2/Dockerfile
@@ -0,0 +1,30 @@
+#  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.
+
+FROM node:8
+
+WORKDIR /usr/src/app
+
+COPY package*.json ./
+
+RUN npm install
+
+COPY . . 
+
+EXPOSE 8080
+
+CMD ["npm", "start"]
+
diff --git a/k8s/backend/node-app-2/hello.html b/k8s/backend/node-app-2/hello.html
new file mode 100644
index 0000000..0b01d6d
--- /dev/null
+++ b/k8s/backend/node-app-2/hello.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<HTML>
+
+<HEAD>
+    <TITLE>
+        A Small Hello
+    </TITLE>
+</HEAD>
+
+<BODY>
+    <H1>Hi</H1>
+    <P>This is very minimal "hello world" HTML document.</P>
+</BODY>
+
+</HTML>
diff --git a/k8s/backend/node-app-2/package-lock.json b/k8s/backend/node-app-2/package-lock.json
new file mode 100644
index 0000000..785a743
--- /dev/null
+++ b/k8s/backend/node-app-2/package-lock.json
@@ -0,0 +1,374 @@
+{
+  "name": "dockernode",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "accepts": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+      "requires": {
+        "mime-types": "~2.1.24",
+        "negotiator": "0.6.2"
+      }
+    },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+    },
+    "body-parser": {
+      "version": "1.19.0",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+      "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+      "requires": {
+        "bytes": "3.1.0",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "1.7.2",
+        "iconv-lite": "0.4.24",
+        "on-finished": "~2.3.0",
+        "qs": "6.7.0",
+        "raw-body": "2.4.0",
+        "type-is": "~1.6.17"
+      }
+    },
+    "bytes": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+      "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
+    },
+    "content-disposition": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+      "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+      "requires": {
+        "safe-buffer": "5.1.2"
+      }
+    },
+    "content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+    },
+    "cookie": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+      "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+    },
+    "destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+    },
+    "express": {
+      "version": "4.17.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+      "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+      "requires": {
+        "accepts": "~1.3.7",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.19.0",
+        "content-disposition": "0.5.3",
+        "content-type": "~1.0.4",
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.1.2",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.5",
+        "qs": "6.7.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.1.2",
+        "send": "0.17.1",
+        "serve-static": "1.14.1",
+        "setprototypeof": "1.1.1",
+        "statuses": "~1.5.0",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      }
+    },
+    "finalhandler": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "statuses": "~1.5.0",
+        "unpipe": "~1.0.0"
+      }
+    },
+    "forwarded": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+    },
+    "http-errors": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+      "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+      "requires": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.3",
+        "setprototypeof": "1.1.1",
+        "statuses": ">= 1.5.0 < 2",
+        "toidentifier": "1.0.0"
+      }
+    },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+    },
+    "ipaddr.js": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
+      "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+    },
+    "mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+    },
+    "mime-db": {
+      "version": "1.40.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+      "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
+    },
+    "mime-types": {
+      "version": "2.1.24",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+      "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+      "requires": {
+        "mime-db": "1.40.0"
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "negotiator": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
+    "proxy-addr": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
+      "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
+      "requires": {
+        "forwarded": "~0.1.2",
+        "ipaddr.js": "1.9.0"
+      }
+    },
+    "qs": {
+      "version": "6.7.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+      "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
+    },
+    "range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+    },
+    "raw-body": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+      "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+      "requires": {
+        "bytes": "3.1.0",
+        "http-errors": "1.7.2",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "send": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+      "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "destroy": "~1.0.4",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "~1.7.2",
+        "mime": "1.6.0",
+        "ms": "2.1.1",
+        "on-finished": "~2.3.0",
+        "range-parser": "~1.2.1",
+        "statuses": "~1.5.0"
+      },
+      "dependencies": {
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+        }
+      }
+    },
+    "serve-static": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+      "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.17.1"
+      }
+    },
+    "setprototypeof": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+      "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
+    },
+    "statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
+    },
+    "toidentifier": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+      "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
+    },
+    "type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      }
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+    }
+  }
+}
diff --git a/k8s/backend/node-app-2/package.json b/k8s/backend/node-app-2/package.json
new file mode 100644
index 0000000..e5202ed
--- /dev/null
+++ b/k8s/backend/node-app-2/package.json
@@ -0,0 +1,14 @@
+{
+  "name": "dockernode",
+  "version": "1.0.0",
+  "description": "docker-ize a very simple express.js app",
+  "main": "server.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "max yao",
+  "license": "ISC",
+  "dependencies": {
+    "express": "^4.17.1"
+  }
+}
diff --git a/k8s/backend/node-app-2/server.js b/k8s/backend/node-app-2/server.js
new file mode 100644
index 0000000..3b4b42f
--- /dev/null
+++ b/k8s/backend/node-app-2/server.js
@@ -0,0 +1,36 @@
+// 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.
+
+'use strict';
+
+const express = require('express');
+
+// Constants
+const PORT = 8080;
+const HOST = '0.0.0.0';
+
+// App
+const app = express();
+app.get('/', (req, res) => {
+  res.send('Hello world\n');
+});
+
+app.get('/test', (req, res) => {
+  res.sendFile('hello.html', {root: __dirname });
+})
+
+app.get('/app2', (req, res) => {
+  res.sendFile('hello.html', {root: __dirname });
+})
+
+app.listen(PORT, HOST);
+console.log(`Running on http://${HOST}:${PORT}`);
diff --git a/k8s/configmaps/ats-configmap.yaml b/k8s/configmaps/ats-configmap.yaml
new file mode 100644
index 0000000..7d66ddd
--- /dev/null
+++ b/k8s/configmaps/ats-configmap.yaml
@@ -0,0 +1,33 @@
+#  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.
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: trafficserver-test
+
+---
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  namespace: trafficserver-test
+  name: ats
+data:
+  # reloadable data only
+  proxy.config.output.logfile.rolling_enabled: "1"
+  proxy.config.output.logfile.rolling_interval_sec: "3000"
+  proxy.config.restart.active_client_threshold: "0"
diff --git a/k8s/ingresses/ats-ingress-2.yaml b/k8s/ingresses/ats-ingress-2.yaml
new file mode 100644
index 0000000..ef49f92
--- /dev/null
+++ b/k8s/ingresses/ats-ingress-2.yaml
@@ -0,0 +1,41 @@
+#  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.
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: trafficserver-test-3
+
+---
+
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+  name: app-ingress
+  namespace: trafficserver-test-3
+  annotations:
+    ats.ingress.kubernetes.io/server-snippet: |
+      ts.debug('Debug msg example')
+      ts.error('Error msg example')
+spec:
+  rules:
+  - host: test.edge.com
+    http:
+      paths:
+      - path: /app2
+        backend:
+          serviceName: appsvc2
+          servicePort: 8080
diff --git a/k8s/ingresses/ats-ingress-2s.yaml b/k8s/ingresses/ats-ingress-2s.yaml
new file mode 100644
index 0000000..8396ea9
--- /dev/null
+++ b/k8s/ingresses/ats-ingress-2s.yaml
@@ -0,0 +1,40 @@
+#  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.
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: trafficserver-test-3
+
+---
+
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+  name: app-ingress-https
+  namespace: trafficserver-test-3
+spec:
+  tls:
+  - hosts:
+    - test.edge.com
+  rules:
+  - host: test.edge.com
+    http:
+      paths:
+      - path: /app2
+        backend:
+          serviceName: appsvc2
+          servicePort: 8080
diff --git a/k8s/ingresses/ats-ingress.yaml b/k8s/ingresses/ats-ingress.yaml
new file mode 100644
index 0000000..bddf63b
--- /dev/null
+++ b/k8s/ingresses/ats-ingress.yaml
@@ -0,0 +1,50 @@
+#  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.
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: trafficserver-test-2
+
+---
+
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+  name: app-ingress
+  namespace: trafficserver-test-2
+spec:
+  rules:
+  - host: test.media.com
+    http:
+      paths:
+      - path: /app1
+        backend:
+          serviceName: appsvc1
+          servicePort: 8080
+      - path: /app2
+        backend:
+          serviceName: appsvc2
+          servicePort: 8080
+
+  - host: test.edge.com
+    http:
+      paths:
+      - path: /app1
+        backend:
+          serviceName: appsvc1
+          servicePort: 8080
+          
diff --git a/k8s/traffic-server/ats-deployment.yaml b/k8s/traffic-server/ats-deployment.yaml
new file mode 100644
index 0000000..7e7ef14
--- /dev/null
+++ b/k8s/traffic-server/ats-deployment.yaml
@@ -0,0 +1,103 @@
+#  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.
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: trafficserver-test
+
+---
+
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  labels:
+    app: trafficserver-test
+  name: trafficserver-test
+  namespace: trafficserver-test
+spec:
+  minReadySeconds: 30
+
+  selector:
+    matchLabels:
+      app: trafficserver-test
+
+  # DO NOT EXCEED ONE COPY
+  replicas: 1
+  # DO NOT EXCEED ONE COPY
+  template:
+    metadata:
+      labels:
+        app: trafficserver-test
+    spec:
+
+      containers:
+        - name: trafficserver-test
+          image: ats_alpine:latest # Needs to be updated
+          volumeMounts:
+            - mountPath: "/etc/ats/ssl"
+              name: ats-ssl
+              readOnly: true
+          imagePullPolicy: IfNotPresent
+          env:
+            - name: POD_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.name
+            - name: POD_NAMESPACE
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.namespace
+#            - name: INGRESS_CLASS
+#              value: "ats"
+            - name: POD_TLS_PATH
+              value: "/etc/ats/ssl"
+          ports:
+          - containerPort: 80
+            hostPort: 80
+            name: http
+            protocol: TCP
+          - containerPort: 443
+            hostPort: 443
+            name: https
+            protocol: TCP
+      volumes:
+        - name: ats-ssl
+          secret:
+            secretName: tls-secret
+
+---
+
+apiVersion: v1
+kind: Service
+metadata:
+  name: trafficserver-test
+  namespace: trafficserver-test
+spec:
+  type: NodePort
+  ports:
+  - name: http
+    port: 80
+    protocol: TCP
+    targetPort: 80
+    nodePort: 30000
+  - name: https
+    port: 443
+    protocol: TCP
+    targetPort: 443
+    nodePort: 30043
+  selector:
+    app: trafficserver-test
diff --git a/k8s/traffic-server/ats-rbac.yaml b/k8s/traffic-server/ats-rbac.yaml
new file mode 100644
index 0000000..bb04ba2
--- /dev/null
+++ b/k8s/traffic-server/ats-rbac.yaml
@@ -0,0 +1,60 @@
+#  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.
+
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: trafficserver-test
+
+---
+
+apiVersion: rbac.authorization.k8s.io/v1beta1
+kind: ClusterRole
+metadata:
+  name: trafficserver-test
+rules:
+- apiGroups:
+  - '*'
+  resources:
+  - ingresses
+  - secrets
+  - services
+  - pods
+  - namespaces
+  - replicationcontrollers
+  - endpoints
+  - configmaps
+  verbs:
+  - get
+  - list
+  - watch
+
+---
+
+apiVersion: rbac.authorization.k8s.io/v1beta1
+kind: ClusterRoleBinding
+metadata:
+  name: trafficserver-test
+subjects:
+- kind: ServiceAccount
+  name: default
+  namespace: trafficserver-test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: trafficserver-test
+
diff --git a/logging.config b/logging.config
new file mode 100644
index 0000000..f870505
--- /dev/null
+++ b/logging.config
@@ -0,0 +1,61 @@
+--  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.
+
+-- WebTrends Enhanced Log Format.
+--
+-- The following is compatible with the WebTrends Enhanced Log Format.
+-- If you want to generate a log that can be parsed by WebTrends
+-- reporting tools, simply create a log that uses this format.
+welf = format {
+  Format = 'id=firewall time="%<cqtd> %<cqtt>" fw=%<phn> pri=6 proto=%<cqus> duration=%<ttmsf> sent=%<psql> rcvd=%<cqhl> src=%<chi> dst=%<shi> dstname=%<shn> user=%<caun> op=%<cqhm> arg="%<cqup>" result=%<pssc> ref="%<{Referer}cqh>" agent="%<{user-agent}cqh>" cache=%<crc>'
+}
+
+-- Squid Log Format with seconds resolution timestamp.
+--
+-- The following is the squid format but with a seconds-only timestamp
+-- (cqts) instead of a seconds and milliseconds timestamp (cqtq).
+squid_seconds_only_timestamp = format {
+  Format = '%<cqts> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<cquc> %<caun> %<phr>/%<pqsn> %<psct>'
+}
+
+-- Squid Log Format.
+squid = format {
+--  Format = '%<cqtq> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<cquc> %<caun> %<phr>/%<pqsn> %<psct>'
+  Format = 'ts=%<cqtq> url=%<cqu> host=%<{Host}cqh> duration=%<ttms> status=%<pssc> cache=%<crc> ostatus=%<sssc> uurl=%<cquuc> conn=%<cfsc>/%<pfsc> ip=%<chi> cqhm=%<cqhm> pscl=%<pscl> uuid=%<{X-Yahoo-Request-Id}pqh> bucket=%<{Bucket}cqh> ybucket="%<{Y-Bucket}cqh>" dcdevice=%<{X-Yahoo-Dc-Device-Type}cqh> dcrobot=%<{X-Yahoo-Dc-Robot}cqh> loggedin=%<{X-Yahoo-Logged-In}cqh> dcbver=%<{X-Yahoo-Dc-Browser-Version}cqh> dcosname="%<{X-Yahoo-Dc-Os-Name}cqh>" dcosver=%<{X-Yahoo-Dc-Os-Version}cqh> d [...]
+}
+
+-- Common Log Format.
+common = format {
+  Format = '%<chi> - %<caun> [%<cqtn>] "%<cqtx>" %<pssc> %<pscl>'
+}
+
+-- Extended Log Format.
+extended = format {
+  Format = '%<chi> - %<caun> [%<cqtn>] "%<cqtx>" %<pssc> %<pscl> %<sssc> %<sscl> %<cqcl> %<pqcl> %<cqhl> %<pshl> %<pqhl> %<sshl> %<tts>'
+}
+
+-- Extended2 Log Formats
+extended2 = format {
+  Format = '%<chi> - %<caun> [%<cqtn>] "%<cqtx>" %<pssc> %<pscl> %<sssc> %<sscl> %<cqcl> %<pqcl> %<cqhl> %<pshl> %<pqhl> %<sshl> %<tts> %<phr> %<cfsc> %<pfsc> %<crc>'
+}
+
+log.ascii {
+    Format = squid,
+    Filename = 'squid'
+}
+
+-- vim: set ft=lua :
+
diff --git a/logrotate.ingress b/logrotate.ingress
new file mode 100644
index 0000000..bb730ec
--- /dev/null
+++ b/logrotate.ingress
@@ -0,0 +1,23 @@
+# 
+# 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.
+#
+/usr/local/var/log/ingress_ats.err {
+  daily
+  rotate 2
+  copytruncate
+  missingok
+  compress
+}
diff --git a/main/main.go b/main/main.go
new file mode 100644
index 0000000..4d5fdb7
--- /dev/null
+++ b/main/main.go
@@ -0,0 +1,174 @@
+/*
+
+   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 main
+
+import (
+	"flag"
+	"log"
+	"os"
+	"os/signal"
+	"strings"
+	"syscall"
+
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/rest"
+
+	"k8s.io/client-go/tools/clientcmd"
+
+	_ "k8s.io/client-go/util/workqueue"
+
+	_ "k8s.io/api/extensions/v1beta1"
+
+	ep "ingress-ats/endpoint"
+	"ingress-ats/namespace"
+	"ingress-ats/proxy"
+	"ingress-ats/redis"
+	w "ingress-ats/watcher"
+)
+
+var (
+	apiServer          = flag.String("apiServer", "http://notfound.com/", "The kubernetes api server. It should be set if useKubeConfig is set to false. Setting to a dummy value to prevent accidental usage.")
+	useKubeConfig      = flag.Bool("useKubeConfig", false, "Set to true to use kube config passed in kubeconfig arg.")
+	useInClusterConfig = flag.Bool("useInClusterConfig", true, "Set to false to opt out incluster config passed in kubeconfig arg.")
+	kubeconfig         = flag.String("kubeconfig", "/usr/local/etc/k8s/k8s.config", "Absolute path to the kubeconfig file. Only works if useKubeConfig is set to true.")
+
+	certFilePath = flag.String("certFilePath", "/etc/pki/tls/certs/kube-router.pem", "Absolute path to kuberouter user cert file.")
+	keyFilePath  = flag.String("keyFilePath", "/etc/pki/tls/private/kube-router-key.pem", "Absolute path to kuberouter user key file.")
+	caFilePath   = flag.String("caFilePath", "/etc/pki/tls/certs/ca.pem", "Absolute path to k8s cluster ca file.")
+
+	namespaces       = flag.String("namespaces", namespace.ALL, "Comma separated list of namespaces to watch for ingress and endpoints.")
+	ignoreNamespaces = flag.String("ignoreNamespaces", "", "Comma separated list of namespaces to ignore for ingress and endpoints.")
+
+	atsNamespace    = flag.String("atsNamespace", "default", "Name of Namespace the ATS pod resides.")
+	atsIngressClass = flag.String("atsIngressClass", "", "Ingress Class of Ingress object that ATS will retrieve routing info from")
+)
+
+func init() {
+	flag.Parse()
+}
+
+func main() {
+	var (
+		config                           *rest.Config
+		err                              error
+		namespaceMap, ignoreNamespaceMap map[string]bool
+	)
+
+	if *atsNamespace == "" {
+		log.Panicln("Not all required args given.")
+	}
+
+	namespaceMap = make(map[string]bool)
+
+	// default namespace to "all"
+	if *namespaces != namespace.ALL {
+		namespaceList := strings.Split(strings.Replace(strings.ToLower(*namespaces), " ", "", -1), ",")
+		for _, namespace := range namespaceList {
+			if namespace != "" {
+				namespaceMap[namespace] = true
+			}
+		}
+	}
+
+	ignoreNamespaceMap = make(map[string]bool)
+
+	if *ignoreNamespaces != "" {
+		ignoreNamespaceList := strings.Split(strings.Replace(strings.ToLower(*ignoreNamespaces), " ", "", -1), ",")
+		for _, namespace := range ignoreNamespaceList {
+			if namespace != "" {
+				ignoreNamespaceMap[namespace] = true
+			}
+		}
+	}
+
+	if *useKubeConfig {
+		log.Println("Read config from ", *kubeconfig)
+		/* For running outside of the cluster
+		uses the current context in kubeconfig */
+		config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig)
+		if err != nil {
+			log.Panicln(err.Error())
+		}
+		/* for running inside the cluster */
+	} else if *useInClusterConfig {
+		config, err = rest.InClusterConfig()
+		if err != nil {
+			log.Panicln("failed to create InClusterConfig: " + err.Error())
+		}
+	} else {
+		/* create config and set necessary parameters */
+		config = &rest.Config{}
+		config.Host = *apiServer
+		config.CertFile = *certFilePath
+		config.KeyFile = *keyFilePath
+		config.CAFile = *caFilePath
+	}
+
+	/* creates the clientset */
+	clientset, err := kubernetes.NewForConfig(config)
+	if err != nil {
+		log.Panic(err.Error())
+	}
+
+	stopChan := make(chan struct{})
+
+	// ------------ Resolving Namespaces --------------------------------------
+	nsManager := namespace.NsManager{
+		NamespaceMap:       namespaceMap,
+		IgnoreNamespaceMap: ignoreNamespaceMap,
+	}
+
+	nsManager.Init()
+
+	//------------ Setting up Redis in memory Datastructure -------------------
+	rClient, err := redis.Init()
+	if err != nil {
+		log.Panicln("Redis Error: ", err)
+	}
+
+	// IMPORTANT:
+	// We're assuming ingress must be using EXTENSIONS V1BETA1 API
+	// ALL services must be using CORE V1 API
+	endpoint := ep.Endpoint{
+		RedisClient: rClient,
+		ATSManager:  &proxy.ATSManager{Namespace: *atsNamespace, IngressClass: *atsIngressClass},
+		NsManager:   &nsManager,
+	}
+
+	watcher := w.Watcher{
+		Cs:           clientset,
+		ATSNamespace: *atsNamespace,
+		Ep:           &endpoint,
+		StopChan:     stopChan,
+	}
+
+	err = watcher.Watch()
+	if err != nil {
+		log.Panicln("Error received from watcher.Watch() :", err)
+	}
+
+	/* Program termination */
+	signalChan := make(chan os.Signal, 1)
+	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
+	for {
+		select {
+		case <-signalChan:
+			log.Println("Shutdown signal received, exiting...")
+			close(stopChan)
+			os.Exit(0)
+		}
+	}
+}
diff --git a/namespace/namespace.go b/namespace/namespace.go
new file mode 100644
index 0000000..e6030e3
--- /dev/null
+++ b/namespace/namespace.go
@@ -0,0 +1,53 @@
+/*
+
+   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 namespace
+
+// import v1 "k8s.io/api/core/v1"
+
+// ALL Namespaces constant for unified text
+const ALL string = "all"
+
+// NsManager is a function wrapper that is used to determine
+// if a namespace n should be included
+type NsManager struct {
+	NamespaceMap       map[string]bool
+	IgnoreNamespaceMap map[string]bool
+	allNamespaces      bool
+}
+
+// IncludeNamespace is exported method to determine if a
+// namespace should be included
+func (m *NsManager) IncludeNamespace(n string) bool {
+	if m.allNamespaces {
+		_, prs := m.IgnoreNamespaceMap[n]
+		return !prs
+	}
+	_, ns_prs := m.NamespaceMap[n]
+	_, ins_prs := m.IgnoreNamespaceMap[n]
+	if ns_prs && !ins_prs {
+		return ns_prs
+	}
+	return false
+}
+
+// Init initializes NsManager's Namespaces
+func (m *NsManager) Init() {
+	if len(m.NamespaceMap) == 0 {
+		m.allNamespaces = true
+	} else {
+		m.allNamespaces = false
+	}
+}
diff --git a/plugin.config b/plugin.config
new file mode 100644
index 0000000..0810501
--- /dev/null
+++ b/plugin.config
@@ -0,0 +1,17 @@
+#  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.
+
+tslua.so /usr/local/go/bin/src/ingress-ats/pluginats/connect_redis.lua
diff --git a/pluginats/connect_redis.lua b/pluginats/connect_redis.lua
new file mode 100644
index 0000000..4a71426
--- /dev/null
+++ b/pluginats/connect_redis.lua
@@ -0,0 +1,127 @@
+--  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.
+
+ts.add_package_cpath('/usr/local/lib/lua/5.1/socket/?.so;/usr/local/lib/lua/5.1/mime/?.so')
+ts.add_package_path('/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/socket/?.lua')
+
+local redis = require 'redis'
+
+-- connecting to unix domain socket
+local client = redis.connect('unix:///var/run/redis/redis.sock')
+
+-- helper function to split a string
+function ipport_split(s, delimiter)
+  result = {}
+  if (s ~= nil and s ~= '') then
+    for match in (s..delimiter):gmatch("(.-)"..delimiter) do
+      table.insert(result, match)
+    end
+  end
+  return result
+end
+
+---------------------------------------------
+----------------- DO_REMAP ------------------
+---------------------------------------------
+function do_global_read_request()
+  ts.debug("In do_global_read_request()==========")
+  local response = client:ping()
+
+  -- if cannot connect to redis client, terminate early
+  if not response then 
+    ts.debug("In 'not response: '", response)
+    return 0
+  end
+
+  -- We only care about host, path, and port#
+  local req_scheme = ts.client_request.get_url_scheme() or 'http'
+  local req_host = ts.client_request.get_url_host() or ''
+  local req_path = ts.client_request.get_uri() or ''
+  local req_port = ts.client_request.get_url_port() or ''
+  ts.debug("-----Request-----")
+  ts.debug("req_scheme: "..req_scheme)
+  ts.debug("req_host: " .. req_host)
+  ts.debug("req_port: " .. req_port)
+  ts.debug("req_path: " .. req_path)
+  ts.debug("-----------------")
+
+  local host_path = req_scheme .. "://" .. req_host .. req_path
+  
+  client:select(1) -- go with hostpath table first
+  local svcs = client:smembers(host_path) -- redis blocking call
+  -- host/path not in redis DB
+  if svcs == nil then
+    ts.error("Redis Lookup Failure: svcs == nil for hostpath")
+    return 0 
+  end
+
+  for _, svc in ipairs(svcs) do
+    if svc == nil then
+      ts.error("Redis Lookup Failure: svc == nil for hostpath")
+      return 0
+    end 
+    if string.sub(svc, 1, 1) ~= "$" then
+      ts.debug("routing")
+      client:select(0) -- go with svc table second
+      local ipport = client:srandmember(svc) -- redis blocking call
+      -- svc not in redis DB
+      if ipport == nil then
+        ts.error("Redis Lookup Failure: ipport == nil for svc")
+        return 0
+      end
+
+      -- find protocol, ip , port info
+      local values = ipport_split(ipport, '#');
+      if #values ~= 3 then
+        ts.error("Redis Lookup Failure: wrong format - "..ipport)
+        return 0
+      end
+
+      ts.http.skip_remapping_set(1)
+      ts.client_request.set_url_scheme(values[3])
+      ts.client_request.set_uri(req_path)
+      ts.client_request.set_url_host(values[1])
+      ts.client_request.set_url_port(values[2])
+    end 
+  end
+
+  for _, svc in ipairs(svcs) do               
+    if svc == nil then
+      ts.error("Redis Lookup Failure: svc == nil for hostpath")        
+      return 0                                                       
+    end                                                                     
+    if string.sub(svc, 1, 1) == "$" then                               
+      ts.debug("snippet")                    
+      client:select(1)           
+      local snippets = client:smembers(svc)        
+                                
+      if snippets == nil then
+        ts.error("Redis Lookup Failure: snippets == nil for hostpath")
+        return 0      
+      end                                    
+            
+      local snippet = snippets[1]
+      if snippet == nil then
+        ts.error("Redis Lookup Failure: snippet == nil for hostpath")
+        return 0                                                 
+      end                                                
+                                                    
+      local f = loadstring(snippet)                      
+      f()                      
+    end                               
+  end                               
+
+end
diff --git a/proxy/ats.go b/proxy/ats.go
new file mode 100644
index 0000000..29bc7da
--- /dev/null
+++ b/proxy/ats.go
@@ -0,0 +1,52 @@
+/*
+
+   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 proxy
+
+import (
+	"fmt"
+	"os/exec"
+)
+
+// ATSManager talks to ATS
+// In the future, this is the struct that should manage
+// everything related to ATS
+type ATSManager struct {
+	Namespace    string
+	IngressClass string
+}
+
+func (m *ATSManager) IncludeIngressClass(c string) bool {
+	if m.IngressClass == "" {
+		return true
+	}
+
+	if m.IngressClass == c {
+		return true
+	}
+
+	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())
+	}
+	return fmt.Sprintf("Ran p.Key: %s p.Val: %s --> stdoutStderr: %q", k, v, stdoutStderr), nil
+}
diff --git a/records.config b/records.config
new file mode 100644
index 0000000..5837820
--- /dev/null
+++ b/records.config
@@ -0,0 +1,201 @@
+#  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.
+
+##############################################################################
+# *NOTE*: All options covered in this file should be documented in the docs:
+#
+#    https://docs.trafficserver.apache.org/records.config
+##############################################################################
+
+##############################################################################
+# Thread configurations. Docs:
+#    https://docs.trafficserver.apache.org/records.config#thread-variables
+##############################################################################
+CONFIG proxy.config.exec_thread.autoconfig INT 1
+CONFIG proxy.config.exec_thread.autoconfig.scale FLOAT 1.5
+CONFIG proxy.config.exec_thread.limit INT 2
+CONFIG proxy.config.accept_threads INT 1
+CONFIG proxy.config.task_threads INT 2
+CONFIG proxy.config.cache.threads_per_disk INT 8
+CONFIG proxy.config.exec_thread.affinity INT 1
+
+##############################################################################
+# Specify server addresses and ports to bind for HTTP and HTTPS. Docs:
+#    https://docs.trafficserver.apache.org/records.config#proxy.config.http.server_ports
+##############################################################################
+CONFIG proxy.config.http.server_ports STRING 80 443:ssl 80:ipv6 443:ssl:ipv6
+
+##############################################################################
+# Via: headers. Docs:
+#     https://docs.trafficserver.apache.org/records.config#proxy-config-http-insert-response-via-str
+##############################################################################
+CONFIG proxy.config.http.insert_request_via_str INT 1
+CONFIG proxy.config.http.insert_response_via_str INT 0
+
+##############################################################################
+# Parent proxy configuration, in addition to these settings also see parent.config. Docs:
+#    https://docs.trafficserver.apache.org/records.config#parent-proxy-configuration
+#    https://docs.trafficserver.apache.org/en/latest/admin-guide/files/parent.config.en.html
+##############################################################################
+CONFIG proxy.config.http.parent_proxy_routing_enable INT 0
+CONFIG proxy.config.http.parent_proxy.retry_time INT 300
+CONFIG proxy.config.http.parent_proxy.connect_attempts_timeout INT 30
+CONFIG proxy.config.http.forward.proxy_auth_to_parent INT 0
+CONFIG proxy.config.http.uncacheable_requests_bypass_parent INT 1
+
+##############################################################################
+# HTTP connection timeouts (secs). Docs:
+#    https://docs.trafficserver.apache.org/records.config#http-connection-timeouts
+##############################################################################
+CONFIG proxy.config.http.keep_alive_no_activity_timeout_in INT 120
+CONFIG proxy.config.http.keep_alive_no_activity_timeout_out INT 120
+CONFIG proxy.config.http.transaction_no_activity_timeout_in INT 30
+CONFIG proxy.config.http.transaction_no_activity_timeout_out INT 30
+CONFIG proxy.config.http.transaction_active_timeout_in INT 900
+CONFIG proxy.config.http.transaction_active_timeout_out INT 0
+CONFIG proxy.config.http.accept_no_activity_timeout INT 120
+CONFIG proxy.config.net.default_inactivity_timeout INT 86400
+
+##############################################################################
+# Origin server connect attempts. Docs:
+#    https://docs.trafficserver.apache.org/records.config#origin-server-connect-attempts
+##############################################################################
+CONFIG proxy.config.http.connect_attempts_max_retries INT 3
+CONFIG proxy.config.http.connect_attempts_max_retries_dead_server INT 1
+CONFIG proxy.config.http.connect_attempts_rr_retries INT 3
+CONFIG proxy.config.http.connect_attempts_timeout INT 30
+CONFIG proxy.config.http.post_connect_attempts_timeout INT 1800
+CONFIG proxy.config.http.down_server.cache_time INT 60
+CONFIG proxy.config.http.down_server.abort_threshold INT 10
+
+##############################################################################
+# Negative response caching, for redirects and errors. Docs:
+#    https://docs.trafficserver.apache.org/records.config#negative-response-caching
+##############################################################################
+CONFIG proxy.config.http.negative_caching_enabled INT 0
+CONFIG proxy.config.http.negative_caching_lifetime INT 1800
+
+##############################################################################
+# Proxy users variables. Docs:
+#    https://docs.trafficserver.apache.org/records.config#proxy-user-variables
+##############################################################################
+CONFIG proxy.config.http.insert_client_ip INT 1
+CONFIG proxy.config.http.insert_squid_x_forwarded_for INT 1
+
+##############################################################################
+# Security. Docs:
+#    https://docs.trafficserver.apache.org/records.config#security
+##############################################################################
+CONFIG proxy.config.http.push_method_enabled INT 0
+
+##############################################################################
+# Enable / disable HTTP caching. Useful for testing, but also as an
+# overridable (per remap) config
+##############################################################################
+CONFIG proxy.config.http.cache.http INT 0
+
+##############################################################################
+# Cache control. Docs:
+#    https://docs.trafficserver.apache.org/records.config#cache-control
+#    https://docs.trafficserver.apache.org/en/latest/admin-guide/files/cache.config.en.html
+##############################################################################
+CONFIG proxy.config.http.cache.ignore_client_cc_max_age INT 1
+CONFIG proxy.config.http.cache.cache_responses_to_cookies INT 1
+CONFIG proxy.config.http.cache.cache_urls_that_look_dynamic INT 1
+    # https://docs.trafficserver.apache.org/records.config#proxy-config-http-cache-when-to-revalidate
+CONFIG proxy.config.http.cache.when_to_revalidate INT 0
+    # https://docs.trafficserver.apache.org/records.config#proxy-config-http-cache-required-headers
+CONFIG proxy.config.http.cache.required_headers INT 2
+
+##############################################################################
+# Heuristic cache expiration. Docs:
+#    https://docs.trafficserver.apache.org/records.config#heuristic-expiration
+##############################################################################
+CONFIG proxy.config.http.cache.heuristic_min_lifetime INT 3600
+CONFIG proxy.config.http.cache.heuristic_max_lifetime INT 86400
+CONFIG proxy.config.http.cache.heuristic_lm_factor FLOAT 0.10
+
+##############################################################################
+# Network. Docs:
+#    https://docs.trafficserver.apache.org/records.config#network
+##############################################################################
+CONFIG proxy.config.net.connections_throttle INT 30000
+CONFIG proxy.config.net.max_connections_in INT 30000
+CONFIG proxy.config.net.max_connections_active_in INT 10000
+
+##############################################################################
+# RAM and disk cache configurations. Docs:
+#    https://docs.trafficserver.apache.org/records.config#ram-cache
+#    https://docs.trafficserver.apache.org/en/latest/admin-guide/files/storage.config.en.html
+##############################################################################
+CONFIG proxy.config.cache.ram_cache.size INT -1
+CONFIG proxy.config.cache.ram_cache_cutoff INT 4194304
+    # https://docs.trafficserver.apache.org/records.config#proxy-config-cache-limits-http-max-alts
+CONFIG proxy.config.cache.limits.http.max_alts INT 5
+    # https://docs.trafficserver.apache.org/records.config#proxy-config-cache-max-doc-size
+CONFIG proxy.config.cache.max_doc_size INT 0
+CONFIG proxy.config.cache.min_average_object_size INT 8000
+
+##############################################################################
+# Logging Config. Docs:
+#    https://docs.trafficserver.apache.org/records.config#logging-configuration
+#    https://docs.trafficserver.apache.org/en/latest/admin-guide/files/logging.config.en.html
+##############################################################################
+CONFIG proxy.config.log.logging_enabled INT 3
+CONFIG proxy.config.log.max_space_mb_for_logs INT 25000
+CONFIG proxy.config.log.max_space_mb_headroom INT 1000
+CONFIG proxy.config.log.rolling_enabled INT 1
+CONFIG proxy.config.log.rolling_interval_sec INT 86400
+CONFIG proxy.config.log.rolling_size_mb INT 10
+CONFIG proxy.config.log.auto_delete_rolled_files INT 1
+CONFIG proxy.config.log.periodic_tasks_interval INT 5
+
+##############################################################################
+# These settings control remapping, and if the proxy allows (open) forward proxy or not. Docs:
+#    https://docs.trafficserver.apache.org/records.config#url-remap-rules
+#    https://docs.trafficserver.apache.org/en/latest/admin-guide/files/remap.config.en.html
+##############################################################################
+CONFIG proxy.config.url_remap.remap_required INT 1
+    # https://docs.trafficserver.apache.org/records.config#proxy-config-url-remap-pristine-host-hdr
+CONFIG proxy.config.url_remap.pristine_host_hdr INT 1
+    # https://docs.trafficserver.apache.org/records.config#reverse-proxy
+CONFIG proxy.config.reverse_proxy.enabled INT 1
+
+##############################################################################
+# SSL Termination. Docs:
+#    https://docs.trafficserver.apache.org/records.config#client-related-configuration
+#    https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ssl_multicert.config.en.html
+##############################################################################
+CONFIG proxy.config.ssl.client.verify.server INT 0
+CONFIG proxy.config.ssl.client.CA.cert.filename STRING NULL
+CONFIG proxy.config.ssl.server.cipher_suite STRING ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES [...]
+
+##############################################################################
+# Debugging. Docs:
+#    https://docs.trafficserver.apache.org/records.config#diagnostic-logging-configuration
+##############################################################################
+CONFIG proxy.config.diags.debug.enabled INT 0
+CONFIG proxy.config.diags.debug.tags STRING ts_lua
+# ToDo: Undocumented
+CONFIG proxy.config.dump_mem_info_frequency INT 0
+CONFIG proxy.config.http.slow.log.threshold INT 0
+
+##############################################################################
+# Additional Logging and debugging for now
+##############################################################################
+CONFIG proxy.config.log.logfile_dir STRING /usr/local/var/log/trafficserver/
+
+
diff --git a/redis.conf b/redis.conf
new file mode 100644
index 0000000..7cca60d
--- /dev/null
+++ b/redis.conf
@@ -0,0 +1,20 @@
+#  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.
+
+port 0
+unixsocket /var/run/redis/redis.sock
+unixsocketperm 777
+daemonize yes
diff --git a/redis/redis.go b/redis/redis.go
new file mode 100644
index 0000000..c68ed79
--- /dev/null
+++ b/redis/redis.go
@@ -0,0 +1,191 @@
+/*
+
+   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 (
+	"fmt"
+	"log"
+
+	//	"sync"
+
+	"github.com/go-redis/redis"
+)
+
+// Client stores essential client info
+type Client struct {
+	DefaultDB *redis.Client
+	DBOne     *redis.Client
+}
+
+const (
+	redisSocketAddr string = "/var/run/redis/redis.sock"
+	// RSUCCESS is the success code returned by a Redis op
+	RSUCCESS int64 = 1
+	// RFAIL is the failure code returned by a Redis op
+	RFAIL int64 = 0
+)
+
+// TODO: Currently we have host/path --> ip --> port
+// if ports are the same for host/path, then it might make more sense,
+// in short terms, to have host/path --> ip, host/path --> port instead
+
+// Init initializes the redis clients
+func Init() (*Client, error) {
+	rClient, err := CreateRedisClient() // connecting to redis
+	if err != nil {
+		return nil, fmt.Errorf("Failed connecting to Redis: %s", err.Error())
+	}
+	err = rClient.Flush() // when the program starts, flush all stale memory
+	if err != nil {
+		return nil, fmt.Errorf("Failed to FlushAll: %s", err.Error())
+	}
+	return rClient, nil
+}
+
+// CreateRedisClient establishes connection to redis DB
+func CreateRedisClient() (*Client, error) {
+	defaultDB := redis.NewClient(&redis.Options{
+		Network:  "unix",          // use default Addr
+		Addr:     redisSocketAddr, // connect to domain socket
+		Password: "",              // no password set
+		DB:       0,               // use default DB
+	})
+
+	_, err := defaultDB.Ping().Result()
+	if err != nil {
+		return nil, err
+	}
+
+	dbOne := redis.NewClient(&redis.Options{
+		Network:  "unix",          // use default Addr
+		Addr:     redisSocketAddr, // connect to domain socket
+		Password: "",              // no password set
+		DB:       1,               // use DB number 1
+	})
+
+	_, err = dbOne.Ping().Result()
+	if err != nil {
+		return nil, err
+	}
+
+	return &Client{defaultDB, dbOne}, nil
+}
+
+//--------------------- Default DB: svc port --> []IPport ------------------------
+
+// DefaulDBSAdd does SAdd on Default DB and logs results
+func (c *Client) DefaultDBSAdd(svcport, ipport string) {
+	_, err := c.DefaultDB.SAdd(svcport, ipport).Result()
+	if err != nil {
+		log.Printf("DefaultDB.SAdd(%s, %s).Result() Error: %s\n", svcport, ipport, err.Error())
+	}
+}
+
+// DefaultDBDel does Del on Default DB and logs results
+func (c *Client) DefaultDBDel(svcport string) {
+	// then delete host from Default DB
+	_, err := c.DefaultDB.Del(svcport).Result()
+	if err != nil {
+		log.Printf("DefaultDB.Del(%s).Result() Error: %s\n", svcport, err.Error())
+	}
+}
+
+// DefaultDBSUnionStore does sunionstore on default db
+func (c *Client) DefaultDBSUnionStore(dest, src string) {
+	_, err := c.DefaultDB.SUnionStore(dest, src).Result()
+	if err != nil {
+		log.Printf("DefaultDB.SUnionStore(%s, %s).Result() Error: %s\n", dest, src, err.Error())
+	}
+}
+
+//----------------------- DB One: hostport --> []svc port -------------------------------
+
+// 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())
+	}
+}
+
+// DBOneSRem does SRem on DB One and logs results
+func (c *Client) DBOneSRem(hostport, svcport string) {
+	_, err := c.DBOne.SRem(hostport, svcport).Result()
+	if err != nil {
+		log.Printf("DBOne.SRem(%s, %s).Result() Error: %s\n", hostport, svcport, err.Error())
+	}
+}
+
+// DBOneDel does Del on Default DB and logs results
+func (c *Client) DBOneDel(hostport string) {
+	// then delete host from DB One
+	_, err := c.DBOne.Del(hostport).Result()
+	if err != nil {
+		log.Printf("DBOne.Del(%s).Result() Error: %s\n", hostport, err.Error())
+	}
+}
+
+// DefaultDBSUnionStore does sunionstore on default db
+func (c *Client) DBOneSUnionStore(dest, src string) {
+	_, err := c.DBOne.SUnionStore(dest, src).Result()
+	if err != nil {
+		log.Printf("DBOne.SUnionStore(%s, %s).Result() Error: %s\n", dest, src, err.Error())
+	}
+}
+
+//------------------------- Other ---------------------------------------------
+
+// Flush flushes all of redis database
+func (c *Client) Flush() error {
+	if _, err := c.DefaultDB.FlushAll().Result(); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Close tries to close the 2 clients
+func (c *Client) Close() {
+	c.DefaultDB.Close()
+	c.DBOne.Close()
+	// for garbage collector
+	c.DefaultDB = nil
+	c.DBOne = nil
+}
+
+// Terminate tries to flush the entire redis and close clients
+func (c *Client) Terminate() {
+	c.Flush() // should go first
+	c.Close()
+}
+
+// PrintAllKeys prints all the keys in the redis client. For debugging purposes etc
+func (c *Client) PrintAllKeys() {
+	var (
+		res interface{}
+		err error
+	)
+	if res, err = c.DefaultDB.Do("KEYS", "*").Result(); err != nil {
+		log.Println("Error Printing Default DB (0): ", err)
+	} else {
+		log.Println("DefaultDB.Do(\"KEYS\", \"*\").Result(): ", res)
+	}
+
+	if res, err = c.DBOne.Do("KEYS", "*").Result(); err != nil {
+		log.Println("Error Printing DB One (1): ", err)
+	} else {
+		log.Println("DBOne.Do(\"KEYS\", \"*\").Result(): ", res)
+	}
+}
diff --git a/tls-config.sh b/tls-config.sh
new file mode 100755
index 0000000..dd0dc99
--- /dev/null
+++ b/tls-config.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+
+set +x
+
+if [ -z "${POD_TLS_PATH}" ]; then
+	echo "POD_TLS_PATH not defined"
+	exit 1
+fi
+
+tlspath="$POD_TLS_PATH/"      
+tlskey="$POD_TLS_PATH/tls.key"
+tlscrt="$POD_TLS_PATH/tls.crt"
+        
+if [ ! -f "${tlscrt}" ]; then
+	echo "${tlscrt} not found"
+	exit 1
+fi
+
+if [ ! -f "${tlskey}" ]; then
+	echo "${tlskey} not found"
+	exit 1
+fi
+
+echo "dest_ip=* ssl_cert_name=${tlscrt} ssl_key_name=${tlskey}" > /usr/local/etc/trafficserver/ssl_multicert.config
diff --git a/tls-reload.sh b/tls-reload.sh
new file mode 100755
index 0000000..f7efb89
--- /dev/null
+++ b/tls-reload.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+
+set +x
+
+if [ -z "${POD_TLS_PATH}" ]; then
+	echo "POD_TLS_PATH not defined"
+	exit 1
+fi
+
+tlspath="$POD_TLS_PATH/"      
+tlskey="$POD_TLS_PATH/tls.key"
+tlscrt="$POD_TLS_PATH/tls.crt"
+        
+if [ ! -f "${tlscrt}" ]; then
+	echo "${tlscrt} not found"
+	exit 1
+fi
+                  
+oldcksum=`cksum ${tlscrt}`
+                                                                                        
+inotifywait -e modify,move,create,delete -mr --timefmt '%d/%m/%y %H:%M' --format '%T' \ 
+	${tlspath} | while read date time; do
+
+		newcksum=`cksum ${tlscrt}`              
+		if [ "$newcksum" != "$oldcksum" ]; then                                   
+			echo "At ${time} on ${date}, tls cert/key files update detected." 
+			oldcksum=$newcksum                                      
+			touch /usr/local/etc/trafficserver/ssl_multicert.config 
+			traffic_ctl config reload 
+                fi 
+        done 
+
diff --git a/types/metadataManagers.go b/types/metadataManagers.go
new file mode 100644
index 0000000..0a6e401
--- /dev/null
+++ b/types/metadataManagers.go
@@ -0,0 +1,292 @@
+/*
+
+   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 types
+
+import (
+	"encoding/json"
+	"sync"
+
+	"ingress-ats/util"
+
+	set "github.com/deckarep/golang-set"
+)
+
+//----------------- nsSvc ---------------------------------
+
+// map[namespace]map[svc][]PathPtr
+type nsSvc struct {
+	// ns -> []svc -> []*path
+	NsToSvc map[string]*svcPaths
+	mux     sync.RWMutex
+}
+
+// newNsSvc creates a new empty nsSvc
+func newNsSvc() *nsSvc {
+	return &nsSvc{
+		NsToSvc: make(map[string]*svcPaths),
+	}
+}
+
+// SetNamespaceSvcPaths adds or sets a path/svc within namespace
+func (n *nsSvc) SetNamespaceSvcPaths(namespace string, pathPtr *Path) {
+	n.mux.Lock()
+	if svcPathsPtr, found := n.NsToSvc[namespace]; found {
+		svcPathsPtr.AddSvcPath(pathPtr)
+	} else {
+		newSvcPaths := newSvcPaths()
+		newSvcPaths.AddSvcPath(pathPtr)
+		n.NsToSvc[namespace] = newSvcPaths
+	}
+	n.mux.Unlock()
+}
+
+// DelNamespaceSvcPath deletes a Path ptr from namespace
+func (n *nsSvc) DelNamespaceSvcPath(pathPtr *Path) {
+	n.mux.Lock()
+	defer n.mux.Unlock()
+	namespace := pathPtr.GetNamespace()
+	if svcPathsPtr, found := n.NsToSvc[namespace]; found {
+		deletedSvc := svcPathsPtr.DelSvcPath(pathPtr)
+		if deletedSvc && svcPathsPtr.NumSvc() == 0 {
+			// when ns has no ingress defined deployed service
+			delete(n.NsToSvc, namespace)
+		}
+	}
+}
+
+// NumNamespace returns the number of namespaces being managed
+func (n *nsSvc) NumNamespace() int {
+	n.mux.RLock()
+	defer n.mux.RUnlock()
+	return len(n.NsToSvc)
+}
+
+// HasSvc returns true if namespace exists, and svc exists in
+// same namespace
+func (n *nsSvc) HasSvc(namespace, svcName string) bool {
+	n.mux.RLock()
+	defer n.mux.RUnlock()
+	if svcPaths, found := n.NsToSvc[namespace]; found {
+		return svcPaths.Has(svcName)
+	}
+	return false
+}
+
+// NumHostPathInNamespace returns number of paths of hostName are in namespace
+// TODO: can add more metadata in the future to make this faster
+func (n *nsSvc) NoHostPathInNamespace(hostName, pathName, namespace string) bool {
+	res := 0
+	n.mux.RLock()
+	svcPathsPtr := n.NsToSvc[namespace]
+	n.mux.RUnlock()
+	svcPathsPtr.rLock()
+	for _, pathSet := range svcPathsPtr.StoPs {
+		for path := range pathSet.Iter() {
+			pathPtr := path.(*Path)
+			if pathPtr.GetHostName() == hostName &&
+				pathPtr.GetPathName() == pathName {
+				res++
+			}
+		}
+	}
+	svcPathsPtr.rUnlock()
+	return res == 0
+}
+
+// Iter returns a rangable channel of all Paths using svc, within namespace
+func (n *nsSvc) Iter(namespace, svc string) <-chan interface{} {
+	n.mux.RLock()
+	defer n.mux.RUnlock()
+	return n.NsToSvc[namespace].Iter(svc)
+}
+
+func (n *nsSvc) String() string {
+	n.mux.RLock()
+	defer n.mux.RUnlock()
+	marshalled, _ := json.Marshal(n)
+	return util.FmtMarshalled(marshalled)
+}
+
+//----------------- svcPaths ------------------------------
+
+// serviceName -> []PathPtr
+type svcPaths struct {
+	StoPs map[string]set.Set `json:"StoPs"` // svcName -> []Pathptr
+	mux   sync.RWMutex
+}
+
+// newSvcPaths returns a new empty SvcPaths struct ptr
+func newSvcPaths() *svcPaths {
+	return &svcPaths{StoPs: make(map[string]set.Set)}
+}
+
+func (s *svcPaths) rLock() {
+	s.mux.RLock()
+}
+
+func (s *svcPaths) rUnlock() {
+	s.mux.RUnlock()
+}
+
+// checkAddSvc adds/initialize svc if not stored
+func (s *svcPaths) checkAddSvc(svc string) {
+	s.mux.Lock()
+	if _, found := s.StoPs[svc]; !found {
+		s.StoPs[svc] = set.NewSet()
+	}
+	s.mux.Unlock()
+}
+
+// AddSvcPath adds a Mapping svc --> PathPtr safely
+func (s *svcPaths) AddSvcPath(pathPtr *Path) {
+	// path cannot be updated during this operation
+	pathPtr.RLock()
+	s.checkAddSvc(pathPtr.ServiceName)
+	// v ^ order here cannot switch!
+	s.mux.RLock()
+	s.StoPs[pathPtr.ServiceName].Add(pathPtr)
+	s.mux.RUnlock()
+	pathPtr.RUnlock()
+}
+
+// DelSvcPath deletes a path ptr associated with svc
+// returns true if svc no longer referenced by any path/backend and is deleted
+func (s *svcPaths) DelSvcPath(pathPtr *Path) (deletedSvc bool) {
+	s.mux.Lock()
+	pathPtr.RLock() // path cannot be updated during this operation
+	defer pathPtr.RUnlock()
+	defer s.mux.Unlock()
+	svc := pathPtr.ServiceName
+	if _, found := s.StoPs[svc]; found {
+		s.StoPs[svc].Remove(pathPtr)
+		// if svc no longer needed by any backend
+		if s.StoPs[svc].Cardinality() == 0 {
+			delete(s.StoPs, svc)
+			return true
+		}
+	}
+	return false
+
+}
+
+// Iter returns a channel of path ptrs associated with svc
+// this is meant to be used with "range"
+func (s *svcPaths) Iter(svc string) <-chan interface{} {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.StoPs[svc].Iter() // this itself is threadsafe
+}
+
+// Has returns whether the svc is stored
+func (s *svcPaths) Has(svc string) bool {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	_, found := s.StoPs[svc]
+	return found
+}
+
+// NumSvc returns number of svcs stored
+func (s *svcPaths) NumSvc() int {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return len(s.StoPs)
+}
+
+func (s *svcPaths) String() string {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	marshalled, _ := json.Marshal(s)
+	return util.FmtMarshalled(marshalled)
+}
+
+//----------------- hostNamespaces ------------------------
+
+// hostName -> []namespace
+type hostNamespaces struct {
+	HtoN map[string]set.Set
+	mux  sync.RWMutex
+}
+
+// newHostNamespaces returns a new empty hostNamespaces struct
+func newHostNamespaces() *hostNamespaces {
+	return &hostNamespaces{
+		HtoN: make(map[string]set.Set),
+	}
+}
+
+// checkInit checks of hostName is initialized in map
+func (h *hostNamespaces) checkInit(hostName string) {
+	h.mux.Lock()
+	if _, found := h.HtoN[hostName]; !found {
+		h.HtoN[hostName] = set.NewSet()
+	}
+	h.mux.Unlock()
+}
+
+// AddNamespace adds namespace to hostname's set of namespaces
+// that it belongs to
+func (h *hostNamespaces) AddNamespace(hostName, namespace string) {
+	h.checkInit(hostName)
+	h.mux.RLock()
+	h.HtoN[hostName].Add(namespace)
+	h.mux.RUnlock()
+}
+
+// DelNamespace deletes namespace from hostname's set of namespaces
+// if host no longer belongs in any namespace, function will clear hostname
+// from its map and return true.
+func (h *hostNamespaces) DelNamespace(hostName, namespace string) (deletedHost bool) {
+	h.mux.Lock()
+	defer h.mux.Unlock()
+	_, found := h.HtoN[hostName]
+	if found {
+		h.HtoN[hostName].Remove(namespace)
+		if h.HtoN[hostName].Cardinality() == 0 {
+			delete(h.HtoN, hostName)
+			return true
+		}
+	}
+	return false
+}
+
+// hostPathInNamespace returns true if host has a path in namespace
+func (h *hostNamespaces) hostPathInNamespace(hostName, namespace string) bool {
+	h.mux.RLock()
+	defer h.mux.RUnlock()
+	_, found := h.HtoN[hostName]
+	if found {
+		return h.HtoN[hostName].Contains(namespace)
+	}
+	return false
+}
+
+// hostOnlyInNamespace returns true if host only has path(s) in namespace
+// and nowhere else
+func (h *hostNamespaces) hostOnlyInNamespace(hostName, namespace string) bool {
+	h.mux.RLock()
+	defer h.mux.RUnlock()
+	_, found := h.HtoN[hostName]
+	if !found {
+		// returning false here because host might
+		// very well be deleted by another thread before this function
+		// got ran; in which case, return false so caller don't do
+		// anything that might be dangerous
+		return false
+	}
+	return h.HtoN[hostName].Cardinality() == 1 &&
+		h.HtoN[hostName].Contains(namespace)
+
+}
diff --git a/types/types.go b/types/types.go
new file mode 100644
index 0000000..b7cf504
--- /dev/null
+++ b/types/types.go
@@ -0,0 +1,424 @@
+/*
+
+   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 types
+
+import (
+	"encoding/json"
+	"log"
+	"sync"
+
+	"ingress-ats/util"
+	// set.NewSet() is threadsafe from this package
+	set "github.com/deckarep/golang-set"
+)
+
+// ControllerConfig is the outmost struct storing everything
+// as part of the outputs
+type ControllerConfig struct {
+	// Annotations []SSPair     `json:"annotations"`
+	ConfigGroup *ConfigGroup `json:"configgroup"`
+	HostGroup   *HostGroup   `json:"hostgroup"`
+}
+
+//---------------------------------------------------------
+//----------------- ConfigGroup ---------------------------
+//---------------------------------------------------------
+
+// ConfigGroup stores all ConfigMap settings for ATS
+type ConfigGroup struct {
+	ConfigMap *configMap `json:"configmaps"`
+}
+
+// NewConfigGroup returns an empty new ConfigGroup struct
+func NewConfigGroup() *ConfigGroup {
+	return &ConfigGroup{
+		ConfigMap: newConfigMap(),
+	}
+}
+
+//----------------- ConfigMap -----------------------------
+
+// ConfigMap stores one Configmap
+type configMap struct {
+	Annotations map[string]string `json:"annotations"`
+	Data        map[string]string `json:"data"`
+	mux         sync.RWMutex
+}
+
+type filter func(string) bool
+
+// this is possible because map in Go are references
+func (c *configMap) load(dest, src map[string]string, fn filter) {
+	c.mux.Lock()
+	for k, v := range src {
+		if fn(k) {
+			dest[k] = v
+		}
+	}
+	c.mux.Unlock()
+}
+
+// LoadAnnotations loads everything in input map safely
+func (c *configMap) LoadAnnotations(input map[string]string, fn filter) {
+	c.load(c.Annotations, input, fn)
+}
+
+// LoadData loads everything in input map safely
+func (c *configMap) LoadData(input map[string]string, fn filter) {
+	c.load(c.Data, input, fn)
+}
+
+// DelFromData deletes a key from Data
+func (c *configMap) DelFromData(key string) {
+	c.mux.Lock()
+	delete(c.Data, key)
+	c.mux.Unlock()
+}
+
+// HasKeyVal returns whether this ConfigMap contains a key mapped to val
+func (c *configMap) HasKeyVal(key, val string) bool {
+	c.mux.RLock()
+	defer c.mux.RUnlock()
+	if v, found := c.Data[key]; found {
+		return v == val
+	}
+	return false
+}
+
+// SetData does add and update
+func (c *configMap) SetData(k, v string) {
+	c.mux.Lock()
+	c.Data[k] = v
+	c.mux.Unlock()
+}
+
+// String is toString
+func (c *configMap) String() string {
+	c.mux.RLock()
+	defer c.mux.RUnlock()
+	marshalled, _ := json.Marshal(c)
+	return util.FmtMarshalled(marshalled)
+}
+
+// NewConfigMap creates a new empty ConfigMap struct
+func newConfigMap() *configMap {
+	return &configMap{
+		Annotations: make(map[string]string),
+		Data:        make(map[string]string)}
+}
+
+//---------------------------------------------------------
+//----------------- HostGroup -----------------------------
+//---------------------------------------------------------
+
+// TODO: HostGroup methods should orchestrate everything
+// under it, as well as updating both Hosts, and ServiceMgr
+
+// HostGroup is a collection of Hosts
+type HostGroup struct {
+	Hosts      map[string]*Host `json:"hosts"` // different Hosts e.g. test.akomljen.com
+	ServiceMgr *nsSvc           `json:"-"`
+	HostNsMgr  *hostNamespaces  `json:"-"`
+	mux        sync.RWMutex
+}
+
+// NewHostGroup returns an empty HostGroup struct
+func NewHostGroup() *HostGroup {
+	return &HostGroup{
+		Hosts:      make(map[string]*Host),
+		ServiceMgr: newNsSvc(),
+		HostNsMgr:  newHostNamespaces(),
+	}
+}
+
+// hasHost returns true if hostName is already stored in HostGroup
+func (h *HostGroup) hasHost(hostName string) bool {
+	h.mux.RLock()
+	defer h.mux.RUnlock()
+	_, found := h.Hosts[hostName]
+	return found
+}
+
+// AddHost adds a new Host -> HostPtr to HostGroup
+func (h *HostGroup) AddHost(hostName string, hostPtr *Host) {
+	if h.hasHost(hostName) {
+		log.Panicf("HostGroup::AddHost(%s) existing host", hostName)
+	}
+	h.mux.Lock()
+	h.Hosts[hostName] = hostPtr
+	h.mux.Unlock()
+}
+
+// DelHost safely deletes a hostName
+func (h *HostGroup) DelHost(hostName string) {
+	h.mux.RLock()
+	targetHost := h.Hosts[hostName]
+	h.mux.RUnlock()
+	targetHost.lock()
+	for _, pathPtr := range targetHost.Paths {
+		h.ServiceMgr.DelNamespaceSvcPath(pathPtr)
+	}
+	targetHost.unlock()
+	h.mux.Lock()
+	delete(h.Hosts, hostName)
+	h.mux.Unlock()
+}
+
+// GetHost returns Host of hostName
+func (h *HostGroup) GetHost(hostName string) *Host {
+	h.mux.RLock()
+	defer h.mux.RUnlock()
+	return h.Hosts[hostName]
+}
+
+// HostPathInNamespace returns true if host belongs in namespace
+func (h *HostGroup) HostPathInNamespace(hostName, namespace string) bool {
+	return h.HostNsMgr.hostPathInNamespace(hostName, namespace)
+}
+
+// HostOnlyInNamespace returns true if host only has path(s) in namespace
+// and nowhere else
+func (h *HostGroup) HostOnlyInNamespace(hostName, namespace string) bool {
+	return h.HostNsMgr.hostOnlyInNamespace(hostName, namespace)
+}
+
+//----------------- Host ----------------------------------
+
+// Host stores a host name to ALL of its paths
+// From Ingress Resource
+// NOTE: if too many paths exist e.g. x/  x/y  x/y/z etc. maybe implement trees
+type Host struct { // a single Host
+	HostName string           `json:"hostname"` // host name
+	Paths    map[string]*Path `json:"paths"`    // a list of paths
+	mux      sync.RWMutex
+}
+
+// NewHost returns a new empty Host struct
+func NewHost(hostName string) *Host {
+	return &Host{
+		HostName: hostName,
+		Paths:    make(map[string]*Path),
+	}
+}
+
+// lock locks up Host
+func (h *Host) lock() {
+	h.mux.Lock()
+}
+
+// unlock once done
+func (h *Host) unlock() {
+	h.mux.Unlock()
+}
+
+// hasPath return true if path exists under Host
+func (h *Host) hasPath(pathName string) bool {
+	h.mux.RLock()
+	defer h.mux.RUnlock()
+	_, found := h.Paths[pathName]
+	return found
+}
+
+// GetPath safely returns the Path ptr under pathName
+func (h *Host) GetPath(pathName string) *Path {
+	h.mux.RLock()
+	defer h.mux.RUnlock()
+	return h.Paths[pathName]
+}
+
+// AddPath adds a new pathName -> pathPtr
+func (h *Host) AddPath(pathName string, pathPtr *Path) {
+	if h.hasPath(pathName) {
+		log.Panicf("Host::AddPath(%s, pathPtr) already exists", pathName)
+	}
+	h.mux.Lock()
+	h.Paths[pathName] = pathPtr
+	h.mux.Unlock()
+}
+
+// DelPath safely deletes a pathName
+func (h *Host) DelPath(pathName string) {
+	h.mux.Lock()
+	delete(h.Paths, pathName)
+	h.mux.Unlock()
+}
+
+// HasDuplicatePath returns true if pathName is defined in a namespace that is
+// *different* from newNamespace
+func (h *Host) HasDuplicatePath(pathName, newNamespace string) bool {
+	h.mux.RLock()
+	defer h.mux.RUnlock()
+	if pathPtr, found := h.Paths[pathName]; found {
+		return pathPtr.GetNamespace() != newNamespace
+	}
+	return false
+}
+
+//----------------- Path ----------------------------------
+
+// Path is a specific path e.g. /api that stores some request data
+// as well as services associated with the path.
+// From Ingress Resource
+type Path struct { // A single Path
+	HostName    string  `json:"hostname"`    // host name of this path
+	PathName    string  `json:"pathname"`    // path name
+	Namespace   string  `json:"namespace"`   // namespace this path is in
+	ServiceName string  `json:"servicename"` // service name associated with path name
+	ServicePort string  `json:"serviceport"` // port of referenced service
+	Server      *Server `json:"server"`      // A list of services
+	mux         sync.RWMutex
+}
+
+// NewPath constructs and returns a ptr to a new path struct
+func NewPath(hostName, pathName, namespace, serviceName, servicePort string,
+	server *Server) *Path {
+	return &Path{
+		HostName:    hostName,
+		PathName:    pathName,
+		Namespace:   namespace,
+		ServiceName: serviceName,
+		ServicePort: servicePort,
+		Server:      server,
+	}
+}
+
+func (p *Path) String() string {
+	p.mux.RLock()
+	defer p.mux.RUnlock()
+	marshalled, _ := json.Marshal(p)
+	return util.FmtMarshalled(marshalled)
+}
+
+// RLock used by other struct
+func (p *Path) RLock() {
+	p.mux.RLock()
+}
+
+// RUnlock when done
+func (p *Path) RUnlock() {
+	p.mux.RUnlock()
+}
+
+// GetHostName returns HostName of this Path
+// this never changes, no locking
+func (p *Path) GetHostName() string {
+	return p.HostName
+}
+
+// GetPathName returns Path Name of this Path
+// this never changes, no locking
+func (p *Path) GetPathName() string {
+	return p.PathName
+}
+
+// GetNamespace returns the namespace this Path belongs to
+// this never changes, no locking
+func (p *Path) GetNamespace() string {
+	return p.Namespace
+}
+
+// GetServiceName returns serviceName of this Path
+func (p *Path) GetServiceName() string {
+	p.mux.RLock()
+	defer p.mux.RUnlock()
+	return p.ServiceName
+}
+
+// SetService safely sets the serviceName of this Path
+// this also includes updating the server!
+func (p *Path) SetService(serviceName string, server *Server) {
+	p.mux.Lock()
+	p.ServiceName = serviceName
+	p.Server = server
+	p.mux.Unlock()
+}
+
+// GetServicePort returns servicePort of this Path
+func (p *Path) GetServicePort() string {
+	p.mux.RLock()
+	defer p.mux.RUnlock()
+	return p.ServicePort
+}
+
+// SetServicePort safely sets the servicePort this Path
+func (p *Path) SetServicePort(servicePort string) {
+	p.mux.Lock()
+	p.ServicePort = servicePort
+	p.mux.Unlock()
+}
+
+// InNamespace returns true if Path belongs in namespace
+// this never changes, no locking
+func (p *Path) InNamespace(namespace string) bool {
+	return p.Namespace == namespace
+}
+
+//----------------- Server --------------------------------
+
+// Server stores essential info of each service and how to reach it.
+// From Endpoint Resource
+type Server struct {
+	IPAddresses set.Set `json:"ipaddresses"` // []string IP address IPV4 or IPV6
+	Ports       set.Set `json:"ports"`       // []Ports port number of the pod
+}
+
+// NewServer returns a new emtpy server struct
+func NewServer() *Server {
+	return &Server{
+		IPAddresses: set.NewSet(),
+		Ports:       set.NewSet(),
+	}
+}
+
+// Port stores job of each port number
+// From Endpoint Resource
+type Port struct {
+	Name     string `json:"name"`
+	Port     string `json:"port"`
+	Protocol string `json:"protocol"` // TCP, UDP, HTTP etc
+}
+
+//---------------------------------------------------------
+//-------------- Helper [unused] --------------------------
+//---------------------------------------------------------
+
+// SSPair stores key val String pairs of one annotation
+// Annotations is an unstructured key value map stored with a resource that may be
+// set by external tools to store and retrieve arbitrary metadata. They are not
+// queryable and should be preserved when modifying objects.
+// More info: http://kubernetes.io/docs/user-guide/annotations
+type SSPair struct {
+	Key string `json:"key"`
+	Val string `json:"val"`
+}
+
+// CreateSSPairs constructs an array of SSPair using a string string map and
+// a filter function
+func CreateSSPairs(m map[string]string, filter func(string) bool) []SSPair {
+	if m != nil && len(m) > 0 {
+		var res []SSPair
+		for k, v := range m {
+			if filter(k) {
+				res = append(res, SSPair{
+					Key: k,
+					Val: v,
+				})
+			}
+		}
+		return res
+	}
+	return nil
+}
diff --git a/util/util.go b/util/util.go
new file mode 100644
index 0000000..587e62a
--- /dev/null
+++ b/util/util.go
@@ -0,0 +1,153 @@
+/*
+
+   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 util
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+
+	//	p "path"
+	"sync"
+)
+
+// Writer writes the JSON file synchronously
+type Writer struct {
+	lock     sync.Mutex
+	DirPath  string
+	FileName string
+}
+
+// Perm is default permission bits of JSON file
+const Perm os.FileMode = 0755
+
+const (
+	// Define annotations we check for in the watched resources
+	AnnotationServerSnippet = "ats.ingress.kubernetes.io/server-snippet"
+	AnnotationIngressClass  = "kubernetes.io/ingress.class"
+)
+
+// SyncWriteJSONFile writes obj, intended to be HostGroup, into a JSON file
+// under filename.
+func (w *Writer) SyncWriteJSONFile(obj interface{}) error {
+	w.lock.Lock()
+	defer w.lock.Unlock()
+	file, err := w.CreateFileIfNotExist()
+	if err != nil {
+		return err
+	}
+	defer file.Close() // file is opened, must close
+
+	content, jsonErr := json.MarshalIndent(obj, "", "\t")
+	if jsonErr != nil {
+		return jsonErr
+	}
+
+	// Making sure repeated writes will actually clear the file before each write
+	err = file.Truncate(0)
+	if err != nil {
+		return err
+	}
+	_, err = file.Seek(0, 0)
+	if err != nil {
+		return err
+	}
+	_, writeErr := file.Write(content)
+	if writeErr != nil {
+		return writeErr
+	}
+	file.Sync() // flushing to disk
+	return nil
+}
+
+// CreateFileIfNotExist checks if fileName in dirPath already exists.
+// if not, such file will be created. If the file exists, it will be opened
+// and its file descriptor returned. So, Caller needs to close the file!
+func (w *Writer) CreateFileIfNotExist() (file *os.File, err error) {
+	file, err = nil, nil
+
+	if _, e := os.Stat(w.DirPath); e == nil { // dirPath exists
+		// fall through
+	} else if os.IsNotExist(e) { // dirPath does not exist
+		err = os.MkdirAll(w.DirPath, Perm)
+	} else { // sys error
+		err = e
+	}
+
+	if err != nil {
+		return
+	}
+
+	// caller is responsible for checking err first before using file anyways
+	file, err = os.OpenFile(w.DirPath+"/"+w.FileName, os.O_CREATE|os.O_RDWR, Perm)
+	return
+}
+
+// ConstructHostPathString constructs the string representation of Host + Path
+func ConstructHostPathString(scheme, host, path string) string {
+	if path == "" {
+		path = "/"
+	}
+	return scheme + "://" + host + path
+	//return p.Clean(fmt.Sprintf("%s/%s", host, path))
+}
+
+// ConstructSvcPortString constructs the string representation of namespace, svc, port
+func ConstructSvcPortString(namespace, svc, port string) string {
+	return namespace + ":" + svc + ":" + port
+}
+
+// ConstructIPPortString constructs the string representation of ip, port
+func ConstructIPPortString(ip, port, protocol string) string {
+	if protocol != "https" {
+		protocol = "http"
+	}
+	return ip + "#" + port + "#" + protocol
+}
+
+func ConstructNameVersionString(namespace, name, version string) string {
+	return "$" + namespace + "/" + name + "/" + version
+}
+
+// Itos : Interface to String
+func Itos(obj interface{}) string {
+	return fmt.Sprintf("%v", obj)
+}
+
+func ExtractServerSnippet(ann map[string]string) (snippet string, err error) {
+
+	server_snippet, ok := ann[AnnotationServerSnippet]
+	if !ok {
+		return "", fmt.Errorf("missing annotation '%s'", AnnotationServerSnippet)
+	}
+
+	return server_snippet, nil
+}
+
+func ExtractIngressClass(ann map[string]string) (class string, err error) {
+
+	ingress_class, ok := ann[AnnotationIngressClass]
+	if !ok {
+		return "", fmt.Errorf("missing annotation '%s'", AnnotationIngressClass)
+	}
+
+	return ingress_class, nil
+}
+
+// FmtMarshalled converts json marshalled bytes to string
+func FmtMarshalled(marshalled []byte) string {
+	return fmt.Sprintf("%q", marshalled)
+}
diff --git a/watcher/handlerConfigmap.go b/watcher/handlerConfigmap.go
new file mode 100644
index 0000000..84bccd1
--- /dev/null
+++ b/watcher/handlerConfigmap.go
@@ -0,0 +1,68 @@
+/*
+
+   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"
+
+	"ingress-ats/endpoint"
+	//	t "ingress-ats/types"
+
+	v1 "k8s.io/api/core/v1"
+)
+
+// CMHandler handles Add Update Delete methods on Configmaps
+type CMHandler struct {
+	ResourceName string
+	Ep           *endpoint.Endpoint
+}
+
+// Add for EventHandler
+func (c *CMHandler) Add(obj interface{}) {
+	c.update(obj)
+}
+
+func (c *CMHandler) update(newObj interface{}) {
+	cm, ok := newObj.(*v1.ConfigMap)
+	if !ok {
+		log.Println("In ConfigMapHandler Update; cannot cast to *v1.ConfigMap")
+		return
+	}
+	for currKey, currVal := range cm.Data {
+		msg, err := c.Ep.ATSManager.ConfigSet(currKey, currVal) // update ATS
+		if err != nil {
+			log.Println(err)
+		} else {
+			log.Println(msg)
+		}
+	}
+}
+
+// Update for EventHandler
+func (c *CMHandler) Update(obj, newObj interface{}) {
+	c.update(newObj)
+}
+
+// Delete for EventHandler
+func (c *CMHandler) Delete(obj interface{}) {
+	// do not handle delete events for now
+	return
+}
+
+// GetResourceName returns the resource name
+func (c *CMHandler) GetResourceName() string {
+	return c.ResourceName
+}
diff --git a/watcher/handlerEndpoint.go b/watcher/handlerEndpoint.go
new file mode 100644
index 0000000..d364c35
--- /dev/null
+++ b/watcher/handlerEndpoint.go
@@ -0,0 +1,144 @@
+/*
+
+   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 (
+	//"encoding/json"
+	"fmt"
+	"log"
+
+	"ingress-ats/endpoint"
+	//	t "ingress-ats/types"
+	"ingress-ats/util"
+
+	v1 "k8s.io/api/core/v1"
+	//	set "github.com/deckarep/golang-set"
+)
+
+// EpHandler implements EventHandler
+type EpHandler struct {
+	ResourceName string
+	Ep           *endpoint.Endpoint
+}
+
+func (e *EpHandler) Add(obj interface{}) {
+	log.Printf("\n\nIn EndpointHandler ADD %#v \n\n", obj)
+	e.add(obj)
+	e.Ep.RedisClient.PrintAllKeys()
+}
+
+func (e *EpHandler) add(obj interface{}) {
+	eps, ok := obj.(*v1.Endpoints)
+	if !ok {
+		log.Println("In EndpointHandler Add; cannot cast to *v1.Endpoints.")
+		return
+	}
+	podSvcName := eps.GetObjectMeta().GetName()
+	namespace := eps.GetNamespace()
+
+	if !e.Ep.NsManager.IncludeNamespace(namespace) {
+		log.Println("Namespace not included")
+		return
+	}
+
+	for _, subset := range eps.Subsets {
+		for _, port := range subset.Ports {
+			portnum := fmt.Sprint(port.Port)
+			portname := port.Name
+			key := util.ConstructSvcPortString(namespace, podSvcName, portnum)
+			for _, addr := range subset.Addresses {
+				v := util.ConstructIPPortString(addr.IP, portnum, portname)
+				e.Ep.RedisClient.DefaultDBSAdd(key, v)
+			}
+		}
+
+	}
+}
+
+// Update for EventHandler
+func (e *EpHandler) Update(obj, newObj interface{}) {
+	log.Printf("\n\nEndpoint Update\n Obj: %#v \n newObj: %#v", obj, newObj)
+	e.update(newObj)
+	e.Ep.RedisClient.PrintAllKeys()
+}
+
+func (e *EpHandler) update(obj interface{}) {
+	eps, ok := obj.(*v1.Endpoints)
+	if !ok {
+		log.Println("In EndpointHandler Add; cannot cast to *v1.Endpoints.")
+		return
+	}
+	podSvcName := eps.GetObjectMeta().GetName()
+	namespace := eps.GetNamespace()
+
+	if !e.Ep.NsManager.IncludeNamespace(namespace) {
+		log.Println("Namespace not included")
+		return
+	}
+
+	for _, subset := range eps.Subsets {
+		for _, port := range subset.Ports {
+			portnum := fmt.Sprint(port.Port)
+			portname := port.Name
+			key := util.ConstructSvcPortString(namespace, podSvcName, portnum)
+			for _, addr := range subset.Addresses {
+				k := "temp_" + key
+				v := util.ConstructIPPortString(addr.IP, portnum, portname)
+				e.Ep.RedisClient.DefaultDBSAdd(k, v)
+			}
+			e.Ep.RedisClient.DefaultDBSUnionStore(key, "temp_"+key)
+			e.Ep.RedisClient.DefaultDBDel("temp_" + key)
+		}
+
+	}
+}
+
+// Delete for EventHandler
+func (e *EpHandler) Delete(obj interface{}) {
+	log.Printf("\n\nEndpoint Delete: %#v \n\n", obj)
+	e.delete(obj)
+	e.Ep.RedisClient.PrintAllKeys()
+}
+
+func (e *EpHandler) delete(obj interface{}) {
+	eps, ok := obj.(*v1.Endpoints)
+	if !ok {
+		log.Println("In EndpointHandler DELETE; cannot cast to *v1.Endpoints.")
+		return
+	}
+	podSvcName := eps.GetObjectMeta().GetName()
+	namespace := eps.GetNamespace()
+
+	if !e.Ep.NsManager.IncludeNamespace(namespace) {
+		log.Println("Namespace not included")
+		return
+	}
+
+	for _, subset := range eps.Subsets {
+		for _, port := range subset.Ports {
+			portnum := fmt.Sprint(port.Port)
+			key := util.ConstructSvcPortString(namespace, podSvcName, portnum)
+			e.Ep.RedisClient.DefaultDBDel(key)
+		}
+
+	}
+
+}
+
+// GetResourceName returns the resource name
+func (e *EpHandler) GetResourceName() string {
+	return e.ResourceName
+}
diff --git a/watcher/handlerIngress.go b/watcher/handlerIngress.go
new file mode 100644
index 0000000..40963ef
--- /dev/null
+++ b/watcher/handlerIngress.go
@@ -0,0 +1,289 @@
+/*
+
+   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 (
+	//	"encoding/json"
+	"log"
+
+	"ingress-ats/endpoint"
+	//	t "ingress-ats/types"
+	"ingress-ats/util"
+
+	v1beta1 "k8s.io/api/extensions/v1beta1"
+)
+
+// IgHandler implements EventHandler
+type IgHandler struct {
+	ResourceName string
+	Ep           *endpoint.Endpoint
+}
+
+func (g *IgHandler) Add(obj interface{}) {
+	log.Printf("\n\nIn INGRESS_HANDLER ADD %#v \n\n", obj)
+	g.add(obj)
+	g.Ep.RedisClient.PrintAllKeys()
+}
+
+func (g *IgHandler) add(obj interface{}) {
+	ingressObj, ok := obj.(*v1beta1.Ingress)
+	if !ok {
+		log.Println("In HandlerIngress Add; cannot cast to *v1beta1.Ingress")
+		return
+	}
+
+	namespace := ingressObj.GetNamespace()
+	ingressClass, _ := util.ExtractIngressClass(ingressObj.GetAnnotations())
+	if !g.Ep.NsManager.IncludeNamespace(namespace) || !g.Ep.ATSManager.IncludeIngressClass(ingressClass) {
+		log.Println("Namespace not included or Ingress Class not matched")
+		return
+	}
+
+	// add the script before adding route
+	snippet, snippetErr := util.ExtractServerSnippet(ingressObj.GetAnnotations())
+	if snippetErr == nil {
+		name := ingressObj.GetName()
+		version := ingressObj.GetResourceVersion()
+		nameversion := util.ConstructNameVersionString(namespace, name, version)
+		g.Ep.RedisClient.DBOneSAdd(nameversion, snippet)
+	}
+
+	tlsHosts := make(map[string]string)
+
+	for _, ingressTLS := range ingressObj.Spec.TLS {
+		for _, tlsHost := range ingressTLS.Hosts {
+			tlsHosts[tlsHost] = "1"
+		}
+	}
+
+	for _, ingressRule := range ingressObj.Spec.Rules {
+		host := ingressRule.Host
+		scheme := "http"
+		if _, ok := tlsHosts[host]; ok {
+			scheme = "https"
+		}
+
+		for _, httpPath := range ingressRule.HTTP.Paths {
+
+			path := httpPath.Path
+			hostPath := util.ConstructHostPathString(scheme, host, path)
+			service := httpPath.Backend.ServiceName
+			port := httpPath.Backend.ServicePort.String()
+			svcport := util.ConstructSvcPortString(namespace, service, port)
+
+			g.Ep.RedisClient.DBOneSAdd(hostPath, svcport)
+
+			if snippetErr == nil {
+				name := ingressObj.GetName()
+				version := ingressObj.GetResourceVersion()
+				nameversion := util.ConstructNameVersionString(namespace, name, version)
+				g.Ep.RedisClient.DBOneSAdd(hostPath, nameversion)
+			}
+		}
+
+	}
+}
+
+// Update for EventHandler
+func (g *IgHandler) Update(obj, newObj interface{}) {
+	log.Printf("\n\nIn INGRESS_HANDLER UPDATE %#v \n\n", newObj)
+	g.update(obj, newObj)
+	g.Ep.RedisClient.PrintAllKeys()
+}
+
+func (g *IgHandler) update(obj, newObj interface{}) {
+	ingressObj, ok := obj.(*v1beta1.Ingress)
+	if !ok {
+		log.Println("In HandlerIngress Update; cannot cast to *v1beta1.Ingress")
+		return
+	}
+
+	newIngressObj, ok := newObj.(*v1beta1.Ingress)
+	if !ok {
+		log.Println("In HandlerIngress Update; cannot cast to *v1beta1.Ingress")
+		return
+	}
+
+	m := make(map[string]string)
+
+	namespace := ingressObj.GetNamespace()
+	ingressClass, _ := util.ExtractIngressClass(ingressObj.GetAnnotations())
+	if g.Ep.NsManager.IncludeNamespace(namespace) && g.Ep.ATSManager.IncludeIngressClass(ingressClass) {
+		log.Println("Old Namespace included")
+
+		_, snippetErr := util.ExtractServerSnippet(ingressObj.GetAnnotations())
+
+		tlsHosts := make(map[string]string)
+
+		for _, ingressTLS := range ingressObj.Spec.TLS {
+			for _, tlsHost := range ingressTLS.Hosts {
+				tlsHosts[tlsHost] = "1"
+			}
+		}
+
+		for _, ingressRule := range ingressObj.Spec.Rules {
+			host := ingressRule.Host
+			scheme := "http"
+			if _, ok := tlsHosts[host]; ok {
+				scheme = "https"
+			}
+
+			for _, httpPath := range ingressRule.HTTP.Paths {
+
+				path := httpPath.Path
+				hostPath := util.ConstructHostPathString(scheme, host, path)
+
+				g.Ep.RedisClient.DBOneSUnionStore("temp_"+hostPath, hostPath)
+				m["temp_"+hostPath] = hostPath
+
+				service := httpPath.Backend.ServiceName
+				port := httpPath.Backend.ServicePort.String()
+				svcport := util.ConstructSvcPortString(namespace, service, port)
+
+				g.Ep.RedisClient.DBOneSRem("temp_"+hostPath, svcport)
+
+				if snippetErr == nil {
+					name := ingressObj.GetName()
+					version := ingressObj.GetResourceVersion()
+					nameversion := util.ConstructNameVersionString(namespace, name, version)
+					g.Ep.RedisClient.DBOneSRem("temp_"+hostPath, nameversion)
+				}
+			}
+
+		}
+	}
+
+	newNamespace := ingressObj.GetNamespace()
+	newIngressClass, _ := util.ExtractIngressClass(ingressObj.GetAnnotations())
+	if g.Ep.NsManager.IncludeNamespace(newNamespace) && g.Ep.ATSManager.IncludeIngressClass(newIngressClass) {
+		log.Println("New Namespace included")
+
+		newSnippet, newSnippetErr := util.ExtractServerSnippet(newIngressObj.GetAnnotations())
+		if newSnippetErr == nil {
+			newName := newIngressObj.GetName()
+			newVersion := newIngressObj.GetResourceVersion()
+			newNameversion := util.ConstructNameVersionString(newNamespace, newName, newVersion)
+			g.Ep.RedisClient.DBOneSAdd(newNameversion, newSnippet)
+		}
+
+		newTlsHosts := make(map[string]string)
+
+		for _, newIngressTLS := range newIngressObj.Spec.TLS {
+			for _, newTlsHost := range newIngressTLS.Hosts {
+				newTlsHosts[newTlsHost] = "1"
+			}
+		}
+
+		for _, ingressRule := range newIngressObj.Spec.Rules {
+			host := ingressRule.Host
+			scheme := "http"
+			if _, ok := newTlsHosts[host]; ok {
+				scheme = "https"
+			}
+
+			for _, httpPath := range ingressRule.HTTP.Paths {
+
+				path := httpPath.Path
+				hostPath := util.ConstructHostPathString(scheme, host, path)
+
+				service := httpPath.Backend.ServiceName
+				port := httpPath.Backend.ServicePort.String()
+				svcport := util.ConstructSvcPortString(namespace, service, port)
+
+				g.Ep.RedisClient.DBOneSAdd("temp_"+hostPath, svcport)
+				m["temp_"+hostPath] = hostPath
+
+				if newSnippetErr == nil {
+					newName := newIngressObj.GetName()
+					newVersion := newIngressObj.GetResourceVersion()
+					newNameversion := util.ConstructNameVersionString(newNamespace, newName, newVersion)
+					g.Ep.RedisClient.DBOneSAdd("temp_"+hostPath, newNameversion)
+				}
+			}
+
+		}
+	}
+
+	for key, value := range m {
+		g.Ep.RedisClient.DBOneSUnionStore(value, key)
+		g.Ep.RedisClient.DBOneDel(key)
+	}
+}
+
+// Delete for EventHandler
+func (g *IgHandler) Delete(obj interface{}) {
+	log.Printf("\n\nIn INGRESS_HANDLER DELETE %#v \n\n", obj)
+	g.delete(obj)
+	g.Ep.RedisClient.PrintAllKeys()
+}
+
+// Helper for Deletes
+func (g *IgHandler) delete(obj interface{}) {
+	ingressObj, ok := obj.(*v1beta1.Ingress)
+	if !ok {
+		log.Println("In HandlerIngress Delete; cannot cast to *v1beta1.Ingress")
+		return
+	}
+
+	namespace := ingressObj.GetNamespace()
+	ingressClass, _ := util.ExtractIngressClass(ingressObj.GetAnnotations())
+	if !g.Ep.NsManager.IncludeNamespace(namespace) || !g.Ep.ATSManager.IncludeIngressClass(ingressClass) {
+		log.Println("Namespace not included or Ingress Class not matched")
+		return
+	}
+
+	_, snippetErr := util.ExtractServerSnippet(ingressObj.GetAnnotations())
+
+	tlsHosts := make(map[string]string)
+
+	for _, ingressTLS := range ingressObj.Spec.TLS {
+		for _, tlsHost := range ingressTLS.Hosts {
+			tlsHosts[tlsHost] = "1"
+		}
+	}
+
+	for _, ingressRule := range ingressObj.Spec.Rules {
+		host := ingressRule.Host
+		scheme := "http"
+		if _, ok := tlsHosts[host]; ok {
+			scheme = "https"
+		}
+
+		for _, httpPath := range ingressRule.HTTP.Paths {
+
+			path := httpPath.Path
+			hostPath := util.ConstructHostPathString(scheme, host, path)
+			service := httpPath.Backend.ServiceName
+			port := httpPath.Backend.ServicePort.String()
+			svcport := util.ConstructSvcPortString(namespace, service, port)
+
+			g.Ep.RedisClient.DBOneSRem(hostPath, svcport)
+
+			if snippetErr == nil {
+				name := ingressObj.GetName()
+				version := ingressObj.GetResourceVersion()
+				nameversion := util.ConstructNameVersionString(namespace, name, version)
+				g.Ep.RedisClient.DBOneSRem(hostPath, nameversion)
+			}
+		}
+
+	}
+}
+
+// GetResourceName returns the resource name
+func (g *IgHandler) GetResourceName() string {
+	return g.ResourceName
+}
diff --git a/watcher/watcher.go b/watcher/watcher.go
new file mode 100644
index 0000000..9ab853d
--- /dev/null
+++ b/watcher/watcher.go
@@ -0,0 +1,137 @@
+/*
+
+   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 (
+	"errors"
+	"fmt"
+	"log"
+	"time"
+
+	v1 "k8s.io/api/core/v1"
+
+	k "k8s.io/client-go/kubernetes"
+
+	"k8s.io/client-go/tools/cache"
+
+	v1beta1 "k8s.io/api/extensions/v1beta1"
+
+	"k8s.io/apimachinery/pkg/fields"
+	pkgruntime "k8s.io/apimachinery/pkg/runtime"
+	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+
+	"ingress-ats/endpoint"
+)
+
+// FIXME: watching all namespace does not work...
+
+// Watcher stores all essential information to act on HostGroups
+type Watcher struct {
+	Cs           *k.Clientset
+	ATSNamespace string
+	Ep           *endpoint.Endpoint
+	StopChan     chan struct{}
+}
+
+// EventHandler interface defines the 3 required methods to implement for watchers
+type EventHandler interface {
+	Add(obj interface{})
+	Update(obj, newObj interface{})
+	Delete(obj interface{})
+	GetResourceName() string // EventHandler should store the ResourceName e.g. ingresses, endpoints...
+}
+
+// Watch creates necessary threads to watch over resources
+func (w *Watcher) Watch() error {
+
+	//================= Watch for Ingress ==================
+	igHandler := IgHandler{"ingresses", w.Ep}
+	err := w.allNamespacesWatchFor(&igHandler, w.Cs.ExtensionsV1beta1().RESTClient(),
+		fields.Everything(), &v1beta1.Ingress{}, 0)
+	if err != nil {
+		return err
+	}
+	//================= Watch for Endpoints =================
+	epHandler := EpHandler{"endpoints", w.Ep}
+	err = w.allNamespacesWatchFor(&epHandler, w.Cs.CoreV1().RESTClient(),
+		fields.Everything(), &v1.Endpoints{}, 0)
+	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)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+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)
+
+	sharedInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc:    h.Add,
+		UpdateFunc: h.Update,
+		DeleteFunc: h.Delete,
+	})
+
+	go sharedInformer.Run(w.StopChan) // new thread
+
+	if !cache.WaitForCacheSync(w.StopChan, sharedInformer.HasSynced) {
+		s := fmt.Sprintf("Timed out waiting for %s caches to sync", h.GetResourceName())
+		utilruntime.HandleError(fmt.Errorf(s))
+		return errors.New(s)
+	}
+	return nil
+}
+
+// This is meant to make it easier to add resource watchers on resources that
+// span multiple namespaces
+func (w *Watcher) inNamespacesWatchFor(h EventHandler, c cache.Getter,
+	namespaces []string, fieldSelector fields.Selector, objType pkgruntime.Object,
+	resyncPeriod time.Duration) 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 {
+		epListWatch := cache.NewListWatchFromClient(c, h.GetResourceName(), ns, fieldSelector)
+		sharedInformer := cache.NewSharedInformer(epListWatch, objType, resyncPeriod)
+
+		sharedInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+			AddFunc:    h.Add,
+			UpdateFunc: h.Update,
+			DeleteFunc: h.Delete,
+		})
+
+		go sharedInformer.Run(w.StopChan) // new thread
+
+		syncFuncs[i] = sharedInformer.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
+}


Mime
View raw message