Initial commit for the affinity router test framework
added license junk.

Change-Id: I6faad2ca93b0a7bb5108a1ffe42ff82f30451ae9
diff --git a/Makefile b/Makefile
index d065cf5..97624e0 100644
--- a/Makefile
+++ b/Makefile
@@ -89,6 +89,9 @@
 afrouter:
 	docker build $(DOCKER_BUILD_ARGS) -t ${REGISTRY}${REPOSITORY}afrouter:${TAG} -f docker/Dockerfile.arouter .
 
+afrouterTest:
+	docker build $(DOCKER_BUILD_ARGS) -t ${REGISTRY}${REPOSITORY}afroutertest:${TAG} -f docker/Dockerfile.arouterTest .
+
 arouterd:
 	docker build $(DOCKER_BUILD_ARGS) -t ${REGISTRY}${REPOSITORY}afrouterd:${TAG} -f docker/Dockerfile.arouterd .
 
diff --git a/afrouter/afrouter/signals.go b/afrouter/afrouter/signals.go
index b178b9a..6cc32ad 100644
--- a/afrouter/afrouter/signals.go
+++ b/afrouter/afrouter/signals.go
@@ -48,6 +48,7 @@
 	sigchan := make(chan os.Signal, 1)
 	signal.Notify(sigchan, os.Interrupt)
 	signal.Notify(sigchan, syscall.SIGTERM)
+	signal.Notify(sigchan, syscall.SIGKILL)
 
 	// Block until we receive a signal on the channel
 	<-sigchan
diff --git a/docker/Dockerfile.arouterTest b/docker/Dockerfile.arouterTest
new file mode 100644
index 0000000..5fe1dc2
--- /dev/null
+++ b/docker/Dockerfile.arouterTest
@@ -0,0 +1,66 @@
+# -------------
+# Build stage
+
+FROM base AS build-env
+
+# Copy files
+ADD rw_core $GOPATH/src/github.com/opencord/voltha-go/rw_core
+ADD common $GOPATH/src/github.com/opencord/voltha-go/common
+ADD db $GOPATH/src/github.com/opencord/voltha-go/db
+ADD kafka $GOPATH/src/github.com/opencord/voltha-go/kafka
+ADD afrouter $GOPATH/src/github.com/opencord/voltha-go/afrouter
+ADD tests/afrouter $GOPATH/src/github.com/opencord/voltha-go/tests/afrouter
+
+# Copy required proto files
+# ... VOLTHA proos
+# Repeat here even if it's done in the base
+ADD protos/*.proto /src/protos/
+ADD protos/scripts/* /src/protos/
+ADD afrouter/arouter.json /src/
+ADD tests/afrouter/suites/* /src/suites/
+ADD tests/afrouter/arouter_test.json /src/
+
+#
+# Copy generated executables here
+WORKDIR $GOPATH/src/github.com/opencord/voltha-go
+
+# Compile protobuf files
+# Repeat here even if it's done in the base
+RUN sh /src/protos/build_protos.sh /src/protos
+
+#ADD /src/protos/voltha.pb test/afrouter/suites
+# Build rw_core
+RUN cd afrouter && go build -o /src/afrouter
+#RUN ls -l /src/protos
+RUN cp /src/protos/voltha.pb tests/afrouter/suites
+RUN cd tests/afrouter && go build -o /src/afrouterTest
+RUN mkdir /src/tests
+RUN cd tests/afrouter/suites && /src/afrouterTest -config main.json
+RUN mkdir /src/temp
+RUN cp -r tests/afrouter/tests /src/temp
+#RUN cd tests/afrouter/tester && go build -o /src/tests
+
+# -------------
+# Image creation stage
+
+FROM alpine:3.6
+
+# Set the working directory
+WORKDIR /app/src
+WORKDIR /app
+
+# Copy required files
+COPY --from=build-env /src/afrouter /app/
+COPY --from=build-env /src/afrouterTest /app/
+COPY --from=build-env /src/arouter.json /app/
+COPY --from=build-env /src/arouter_test.json /app/
+COPY --from=build-env /src/protos/voltha.pb /app/
+COPY --from=build-env /src/suites /app
+COPY --from=build-env /src/tests /app
+COPY --from=build-env /src/temp/ /app/src/
+WORKDIR /app
+
+#CMD cd /app && ./arouter -config config/arouter.voltha2.json
+
+# Set the config volume
+
diff --git a/tests/afrouter/arouter_test.json b/tests/afrouter/arouter_test.json
new file mode 100644
index 0000000..0144478
--- /dev/null
+++ b/tests/afrouter/arouter_test.json
@@ -0,0 +1,184 @@
+{
+    "servers": [
+	    {
+		    "name": "grpc_command",
+			"port": 5000,
+			"address":"",
+			"type": "grpc",
+			"routers": [
+				{
+					"_TODO":"Suport a router list, remove the package and service from the router",
+					"package":"voltha",
+					"service":"VolthaService",
+					"router":"vcore"
+				}
+			]
+		}
+	],
+	"routers": [
+	    {
+		    "name":"vcore",
+			"package": "voltha",
+			"service": "VolthaService",
+            "routes": [
+				{
+					"name":"dev_manager",
+            		"proto_descriptor":"voltha.pb",
+		   			"type":"rpc_affinity_message",
+		   			"association":"round_robin",
+           			"routing_field": "id",
+					"backend_cluster":"vcore",
+					"_COMMENT":"Methods are naturally southbound affinity binding unless otherwise specified below",
+					"methods":[ "CreateDevice",
+								"GetCoreInstance",
+								"EnableLogicalDevicePort",
+								"DisableLogicalDevicePort",
+								"EnableDevice",
+								"DisableDevice",
+								"RebootDevice",
+								"DeleteDevice",
+								"DownloadImage",
+								"CancelImageDownload",
+								"ActivateImageUpdate",
+								"RevertImageUpdate",
+								"UpdateDevicePmConfigs",
+								"CreateAlarmFilter",
+								"UpdateAlarmFilter",
+								"DeleteAlarmFilter",
+								"SelfTest"],
+					"_COMMENT":"If a method is northbound affinity binding then association is used to route",
+					"_COMMENT":"but affinity is not set southbound but only based on the response",
+					"_COMMENT":"Methods here MUST be specified above, this overrides thier default beahvior",
+					"nb_binding_methods":["CreateDevice"],
+					"_TODO":"Overrides not implemented yet, config ignored",
+					"overrides": [
+               			{"methods":["abc","def"], "routing_field":"id"},
+               			{"methods":["ghi","jkl"]},
+               			{"method":"mno", "routing_field":"id"},
+               			{"method":"pqr"}
+					]
+				},
+				{
+					"name":"read_only",
+		   			"type":"round_robin",
+		   			"association":"round_robin",
+					"backend_cluster":"vcore",
+					"methods":[ "ListDevicePorts",
+								"ListDevicePmConfigs",
+								"GetImages",
+								"GetImageDownloadStatus",
+								"GetImageDownload",
+								"ListImageDownloads",
+								"ListDeviceFlows",
+								"ListDeviceFlowGroups",
+								"ListLogicalDeviceFlows",
+								"ListLogicalDeviceFlowGroups",
+								"ListDevices",
+								"GetDevice",
+								"GetDeviceType",
+								"GetDeviceGroup",
+								"GetLogicalDevice",
+								"GetAlarmFilter",
+								"ListLogicalDevicePorts",
+								"GetLogicalDevicePort"
+					]
+				},
+				{
+					"name":"dev_manager_ofagent",
+		   			"type":"binding",
+		   			"_association":"round_robin",
+					"binding": {
+						"type":"header",
+						"field":"voltha_backend_name",
+						"method":"Subscribe",
+						"association":"round_robin"
+					},
+					"backend_cluster":"vcore",
+                    "methods":["StreamPacketsOut",
+                               "Subscribe",
+                               "ListLogicalDevices",
+                               "ListDeviceFlowGroups",
+                               "ListLogicalDeviceFlowGroups",
+                               "ListDeviceFlows",
+                               "UpdateLogicalDeviceFlowTable",
+                               "UpdateLogicalDeviceFlowGroupTable",
+                               "ListLogicalDeviceFlows"
+					],
+					"_TODO":"Overrides not implemented yet, config ignored",
+					"overrides": [
+               			{"methods":["abc","def"], "routing_field":"id"},
+               			{"methods":["ghi","jkl"]},
+               			{"method":"mno", "routing_field":"id"},
+               			{"method":"pqr"}
+					]
+				}
+            ]
+		 }
+	],
+	"backend_clusters": [
+	  {
+        "name":"vcore",
+		"backends":[ {
+			"name":"vcore1",
+			"type":"active_active",
+			"association": {
+				"strategy":"serial_number",
+				"location":"header",
+				"_TODO":"The key below needs to be implemented, currently hard coded",
+				"key":"voltha_serial_number"
+			},
+			"connections": [ {
+		   		"name":"vcore11",
+		   		"addr":"127.0.0.1",
+		   		"port":"5011"
+			},
+			{
+				"name":"vcore12",
+				"addr":"127.0.0.1",
+				"port":"5012"
+			}]
+		},
+		{
+			"name":"vcore2",
+			"type":"active_active",
+			"association": {
+				"strategy":"serial_number",
+				"location":"header"
+			},
+			"connections": [ {
+		   		"name":"vcore21",
+		   		"addr":"127.0.0.1",
+		   		"port":"5021"
+			},
+			{
+				"name":"vcore22",
+				"addr":"127.0.0.1",
+				"port":"5022"
+			}]
+		},
+		{
+			"name":"vcore3",
+			"type":"active_active",
+			"association": {
+				"strategy":"serial_number",
+				"location":"header"
+			},
+			"connections": [ {
+		   		"name":"vcore31",
+		   		"addr":"127.0.0.1",
+		   		"port":"5031"
+			},
+			{
+				"name":"vcore32",
+				"addr":"127.0.0.1",
+				"port":"5032"
+			}]
+		}]
+	  }
+	],
+	"api": {
+		"_comment":"If this isn't defined then no api is available for dynamic configuration and queries",
+		"address":"",
+		"port":55554
+	}
+}
diff --git a/tests/afrouter/suites/main.json b/tests/afrouter/suites/main.json
new file mode 100644
index 0000000..e4862c6
--- /dev/null
+++ b/tests/afrouter/suites/main.json
@@ -0,0 +1,6 @@
+{
+"__COMMENT":"Top level test driver file",
+"suites": [
+	"test1.json"
+]
+}
diff --git a/tests/afrouter/suites/test1.json b/tests/afrouter/suites/test1.json
new file mode 100644
index 0000000..a6791a0
--- /dev/null
+++ b/tests/afrouter/suites/test1.json
@@ -0,0 +1,117 @@
+{
+	"environment":{
+		"cmdLine":"afrouter -config arouter_test.json",
+		"protoFiles": [
+			{
+				"importPath":"github.com/opencord/voltha-go/protos/",
+				"service":"VolthaService",
+				"package":"voltha"
+			}
+		],
+		"imports": [
+			 "github.com/golang/protobuf/ptypes/empty",
+			 "github.com/opencord/voltha-go/protos/openflow_13"
+		],
+		"protoDesc":"voltha.pb",
+		"protoSubst": [
+			{
+				"from":"google.protobuf.Empty",
+				"to":"empty.Empty"
+			}
+		],
+		"clients": [
+			{
+				"name":"client",
+				"port":"5000"
+			}
+		],
+		"servers": [
+			{
+				"name":"core11",
+				"port":"5011"
+			},
+			{
+				"name":"core12",
+				"port":"5012"
+			},
+			{
+				"name":"core21",
+				"port":"5021"
+			},
+			{
+				"name":"core22",
+				"port":"5022"
+			},
+			{
+				"name":"core31",
+				"port":"5031"
+			},
+			{
+				"name":"core32",
+				"port":"5032"
+			}
+		]
+	},
+	"tests":[
+		{
+			"name":"Test CreateDevice",
+			"send": {
+				"client":"client",
+				"method":"CreateDevice",
+				"param":"{Type:\"simulated_olt\"}",
+				"expect":"{Id:\"abcd1234\",Type:\"simulated_olt\"}",
+				"_meta":""
+			},
+			"servers": [
+				{
+					"name":"core11",
+					"meta": [
+						{
+							"key":"voltha_serial_number",
+							"value":"0"
+						}
+					]
+				},
+				{
+					"name":"core12",
+					"meta": [
+						{
+							"key":"voltha_serial_number",
+							"value":"0"
+						}
+					]
+				}
+			]
+		},
+		{
+			"name":"Test GetDevice",
+			"send": {
+				"client":"client",
+				"method":"GetDevice",
+				"param":"{Id:\"abcd1234\"}",
+				"expect":"{Id:\"abcd1234\",Type:\"simulated_olt\"}",
+				"_meta":""
+			},
+			"servers": [
+				{
+					"name":"core11",
+					"meta": [
+						{
+							"key":"voltha_serial_number",
+							"value":"0"
+						}
+					]
+				},
+				{
+					"name":"core12",
+					"meta": [
+						{
+							"key":"voltha_serial_number",
+							"value":"0"
+						}
+					]
+				}
+			]
+		}
+	]
+}
diff --git a/tests/afrouter/suites/test2.json b/tests/afrouter/suites/test2.json
new file mode 100644
index 0000000..2d3d7ad
--- /dev/null
+++ b/tests/afrouter/suites/test2.json
@@ -0,0 +1,87 @@
+{
+	"environment":{
+		"cmdLine":"afrouter -config arouter_test.json",
+		"protoFiles": [
+			{
+				"importPath":"github.com/opencord/voltha-go/protos/",
+				"service":"VolthaService",
+				"package":"voltha"
+			}
+		],
+		"imports": [
+			 "github.com/golang/protobuf/ptypes/empty",
+			 "github.com/opencord/voltha-go/protos/openflow_13"
+		],
+		"protoDesc":"voltha.pb",
+		"protoSubst": [
+			{
+				"from":"google.protobuf.Empty",
+				"to":"empty.Empty"
+			}
+		],
+		"clients": [
+			{
+				"name":"client",
+				"port":"5000"
+			}
+		],
+		"servers": [
+			{
+				"name":"core11",
+				"port":"5011"
+			},
+			{
+				"name":"core12",
+				"port":"5012"
+			},
+			{
+				"name":"core21",
+				"port":"5021"
+			},
+			{
+				"name":"core22",
+				"port":"5022"
+			},
+			{
+				"name":"core31",
+				"port":"5031"
+			},
+			{
+				"name":"core32",
+				"port":"5032"
+			}
+		]
+	},
+	"tests":[
+		{
+			"send": {
+				"client":"client",
+				"method":"CreateDevice",
+				"param":"json struct",
+				"_meta":""
+			},
+			"servers": [
+				{
+					"name":"server11",
+					"meta": [
+						{
+							"key":"voltha_serial_number",
+							"value":"1"
+						}
+					],
+					"param":"mirror_client"
+				},
+				{
+					"name":"server12",
+					"meta": [
+						{
+							"key":"voltha_serial_number",
+							"value":"1"
+						}
+					],
+					"param":"mirror_client"
+				}
+			]
+		}
+	]
+}
diff --git a/tests/afrouter/suites/test3.json b/tests/afrouter/suites/test3.json
new file mode 100644
index 0000000..2d3d7ad
--- /dev/null
+++ b/tests/afrouter/suites/test3.json
@@ -0,0 +1,87 @@
+{
+	"environment":{
+		"cmdLine":"afrouter -config arouter_test.json",
+		"protoFiles": [
+			{
+				"importPath":"github.com/opencord/voltha-go/protos/",
+				"service":"VolthaService",
+				"package":"voltha"
+			}
+		],
+		"imports": [
+			 "github.com/golang/protobuf/ptypes/empty",
+			 "github.com/opencord/voltha-go/protos/openflow_13"
+		],
+		"protoDesc":"voltha.pb",
+		"protoSubst": [
+			{
+				"from":"google.protobuf.Empty",
+				"to":"empty.Empty"
+			}
+		],
+		"clients": [
+			{
+				"name":"client",
+				"port":"5000"
+			}
+		],
+		"servers": [
+			{
+				"name":"core11",
+				"port":"5011"
+			},
+			{
+				"name":"core12",
+				"port":"5012"
+			},
+			{
+				"name":"core21",
+				"port":"5021"
+			},
+			{
+				"name":"core22",
+				"port":"5022"
+			},
+			{
+				"name":"core31",
+				"port":"5031"
+			},
+			{
+				"name":"core32",
+				"port":"5032"
+			}
+		]
+	},
+	"tests":[
+		{
+			"send": {
+				"client":"client",
+				"method":"CreateDevice",
+				"param":"json struct",
+				"_meta":""
+			},
+			"servers": [
+				{
+					"name":"server11",
+					"meta": [
+						{
+							"key":"voltha_serial_number",
+							"value":"1"
+						}
+					],
+					"param":"mirror_client"
+				},
+				{
+					"name":"server12",
+					"meta": [
+						{
+							"key":"voltha_serial_number",
+							"value":"1"
+						}
+					],
+					"param":"mirror_client"
+				}
+			]
+		}
+	]
+}
diff --git a/tests/afrouter/templates/client.go b/tests/afrouter/templates/client.go
new file mode 100644
index 0000000..d384baf
--- /dev/null
+++ b/tests/afrouter/templates/client.go
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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 (
+	"fmt"
+	"errors"
+	"encoding/json"
+	"google.golang.org/grpc"
+	"golang.org/x/net/context"
+	"github.com/opencord/voltha-go/common/log"
+	// Values generated by the go template
+	{{range .Imports}}
+	"{{.}}"
+	{{end}}
+	{{range .ProtoImports}}
+	{{.Short}} "{{.Package}}"
+	{{end}}
+	// End go template values
+)
+
+{{if .Ct}}{{else}}
+type clientCtl struct {
+	send func(string, interface{}, string) error
+	cncl  context.CancelFunc
+	ctx context.Context
+}
+{{end}}
+
+type {{.Name}}ClientConn struct {
+	conn * grpc.ClientConn
+	control * clientCtl
+}
+
+
+var {{.Name}}Client *{{.Name}}ClientConn
+
+
+func {{.Name}}Connect() (*{{.Name}}ClientConn, error)  {
+	log.Infof("Connecting client {{.Name}} to addr:127.0.0.1, port:{{.Port}}")
+	// Dial doesn't block, it just returns and continues connecting in the background.
+	// Check back later to confirm and increase the connection count.
+	cl := &{{.Name}}ClientConn{control:&clientCtl{}}
+	ctx, cnclFnc := context.WithCancel(context.Background())
+	cl.control.cncl = cnclFnc
+	cl.control.ctx = ctx
+	if conn, err := grpc.Dial("127.0.0.1:{{.Port}}", grpc.WithInsecure()); err != nil {
+		log.Errorf("Dialng connection :%v",err)
+		return nil, err
+	} else {
+		cl.conn = conn
+	}
+	{{.Name}}Client = cl
+	cl.control.send = {{.Name}}Send
+	clients["{{.Name}}"] = cl.control
+	return cl, nil
+}
+
+// This function will make the requested RPC with the supplied
+// parameter and validate that the response matches the expected
+// value provided. It will return nil if successful or an error
+// if not.
+func {{.Name}}Send(mthd string, param interface{}, expect string) error {
+	switch mthd {
+	{{range .Methods}}
+		case "{{.Name}}":
+		switch t := param.(type) {
+			case *{{.Param}}:
+	{{if .Ss}}
+			_=t
+	{{else if .Cs}}
+			_=t
+	{{else}}
+			client := {{.Pkg}}.New{{.Svc}}Client({{$.Name}}Client.conn)
+			res, err := client.{{.Name}}(context.Background(), t)
+			if err != nil {
+				return errors.New("Error sending method {{.Name}}")
+			}
+			// Marshal the result and compare it to the expected
+			// value.
+			if resS,err := json.Marshal(res); err == nil {
+				if string(resS) != expect {
+					return errors.New("Unexpected result on method {{.Name}}")
+				}
+			} else {
+				return errors.New("Error Marshaling the reply for method {{.Name}}")
+			}
+			default:
+					return errors.New("Invalid parameter type for method {{.Name}}")
+	{{end}}
+		}
+	{{end}}
+		default:
+			return errors.New(fmt.Sprintf("Unexpected method %s in send", mthd))
+	}
+	return nil
+}
+
diff --git a/tests/afrouter/templates/clientInit.go b/tests/afrouter/templates/clientInit.go
new file mode 100644
index 0000000..e8f4836
--- /dev/null
+++ b/tests/afrouter/templates/clientInit.go
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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
+
+var clients map[string]*clientCtl
+
+func clientInit() error {
+	clients = make(map[string]*clientCtl)
+	{{range .}}
+	if _,err := {{.Name}}Connect(); err != nil {
+		return err
+	}
+	{{end}}
+	return nil
+}
+
+
diff --git a/tests/afrouter/templates/main.go b/tests/afrouter/templates/main.go
new file mode 100644
index 0000000..5c094d9
--- /dev/null
+++ b/tests/afrouter/templates/main.go
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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.
+ */
+// The template for the tester.
+// This template is filled in by the
+// test driver based on the configuration.
+
+package main
+
+import (
+	"os"
+	"time"
+	"os/exec"
+	"strings"
+	"context"
+	"github.com/opencord/voltha-go/common/log"
+)
+
+var resFile *os.File
+
+func startSut(cmdStr string) (context.CancelFunc, error) {
+	var err error = nil
+
+	cmdAry := strings.Fields(cmdStr)
+	log.Infof("Running the affinity router: %s",cmdStr)
+	//ctx, cncl := context.WithCancel(context.Background())
+	ctx, cncl := context.WithCancel(context.Background())
+	cmd := exec.CommandContext(ctx, cmdAry[0], cmdAry[1:]...)
+	cmd.Stdin = os.Stdin
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	if err = cmd.Start(); err != nil {
+		log.Errorf("Failed to run the affinity router: %s %v", cmdStr,err)
+	}
+	time.Sleep(1 * time.Second) // Give the command time to get started
+	return cncl, err
+}
+
+func cleanUp(cncl context.CancelFunc) {
+	cncl()
+	// Give the child processes time to terminate
+	time.Sleep(1 * time.Second)
+}
+
+func main() {
+	var err error
+
+	// Setup logging
+	if _, err = log.SetDefaultLogger(log.JSON, 0, nil); err != nil {
+		log.With(log.Fields{"error": err}).Fatal("Cannot setup logging")
+	}
+
+	defer log.CleanUp()
+
+	// Open the results file to write the results to
+	if resFile, err = os.OpenFile(os.Args[1], os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err != nil {
+		log.Errorf("Could not append to the results file")
+	}
+
+	defer resFile.Close()
+
+	// Initialize the servers
+	if err := serverInit(); err != nil {
+		log.Errorf("Error initializing server: %v", err)
+		return
+	}
+
+	// Start the sofware under test
+	cnclFunc, err := startSut("./{{.Command}}");
+	defer cleanUp(cnclFunc)
+	if  err != nil {
+		return
+	}
+
+	// Initialize the clients
+	if err := clientInit(); err != nil {
+		log.Errorf("Error initializing client: %v", err)
+		return
+	}
+
+	log.Infof("The servers are: %v",servers)
+
+	// Run all the test cases now
+	log.Infof("Executing tests")
+	resFile.Write([]byte("Executing tests\n"))
+	runTests()
+
+}
diff --git a/tests/afrouter/templates/runAll.go b/tests/afrouter/templates/runAll.go
new file mode 100644
index 0000000..b85e4c7
--- /dev/null
+++ b/tests/afrouter/templates/runAll.go
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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.
+ */
+// The template for the tester.
+// This template is filled in by the
+// test driver based on the configuration.
+
+package main
+
+import (
+	"os"
+	//"time"
+	"fmt"
+	"os/exec"
+	"io/ioutil"
+	"github.com/opencord/voltha-go/common/log"
+)
+
+func main() {
+	var cmd *exec.Cmd
+	var cmdStr string
+	// Setup logging
+	if _, err := log.SetDefaultLogger(log.JSON, 0, nil); err != nil {
+		log.With(log.Fields{"error": err}).Fatal("Cannot setup logging")
+	}
+
+	defer log.CleanUp()
+
+	log.Info("Running tests")
+	{{range .}}
+	if err:= os.Chdir(os.Args[1]); err != nil {
+		log.Error("Could not change directory to %s: %v", os.Args[1], err)
+	}
+	cmdStr =  "./"+"{{.}}"[:len("{{.}}")-5]
+	log.Infof("Running test %s",cmdStr)
+	cmd = exec.Command(cmdStr, "results.txt")
+	cmd.Stdin = os.Stdin
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	if err := cmd.Run(); err != nil {
+		log.Errorf("Test '%s' failed", cmdStr)
+	}
+	{{end}}
+	// Open the results file and output it.
+	if resFile, err := ioutil.ReadFile("results.txt"); err == nil {
+		fmt.Println(string(resFile))
+	} else {
+		log.Error("Could not load the results file 'results.txt'")
+	}
+	log.Info("Tests complete")
+}
diff --git a/tests/afrouter/templates/runTests.go b/tests/afrouter/templates/runTests.go
new file mode 100644
index 0000000..3b0f6d4
--- /dev/null
+++ b/tests/afrouter/templates/runTests.go
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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 (
+	"fmt"
+	"time"
+	"errors"
+	"encoding/json"
+	"github.com/opencord/voltha-go/common/log"
+	{{range .Imports}}
+	_ "{{.}}"
+	{{end}}
+	{{range .ProtoImports}}
+	{{.Short}} "{{.Package}}"
+	{{end}}
+)
+
+func runTests() {
+	{{range $k,$v := .Tests}}
+	if err := test{{$k}}(); err == nil {
+		resFile.Write([]byte("\tTest Successful\n"))
+	} else {
+		resFile.Write([]byte("\tTest Failed\n"))
+	}
+	{{end}}
+	time.Sleep(5 * time.Second)
+}
+
+{{range $k,$v := .Tests}}
+func test{{$k}}() error {
+	var rtrn error = nil
+	// Announce the test being run
+	resFile.Write([]byte("******************** Running test case: {{$v.Name}}\n"))
+	// Acquire the client used to run the test
+	cl := clients["{{$v.Send.Client}}"]
+	// Create the server's reply data structure
+	repl := &reply{repl:&{{$v.Send.ExpectType}}{{$v.Send.Expect}}}
+	// Send the reply data structure to each of the servers
+	{{range $s := .Srvr}}
+	if servers["{{$s.Name}}"] == nil {
+		log.Error("Server '{{$s.Name}}' is nil")
+		return errors.New("GAAK")
+	}
+	servers["{{$s.Name}}"].replyData <- repl
+	{{end}}
+
+	// Now call the RPC with the data provided
+	if expct,err := json.Marshal(repl.repl); err != nil {
+		log.Errorf("Marshaling the reply for test {{$v.Name}}: %v",err)
+	} else {
+		if err := cl.send("{{$v.Send.Method}}",
+							&{{$v.Send.ParamType}}{{$v.Send.Param}},
+							string(expct)); err != nil {
+			log.Errorf("Test case {{$v.Name}} failed!: %v", err)
+
+		}
+	}
+
+	// Now read the servers' information to validate it
+	var s *serverCtl
+	var payload string
+	var i *incoming
+	if pld, err := json.Marshal(&{{$v.Send.ParamType}}{{$v.Send.Param}}); err != nil {
+		log.Errorf("Marshaling paramter for test {{$v.Name}}: %v", err)
+	} else {
+		payload = string(pld)
+	}
+	{{range $s := .Srvr}}
+	s = servers["{{$s.Name}}"]
+	i = <-s.incmg
+	if i.payload != payload {
+		log.Errorf("Mismatched payload for test {{$v.Name}}, %s:%s", i.payload, payload)
+		resFile.Write([]byte(fmt.Sprintf("Mismatched payload expected %s, got %s\n", payload, i.payload)))
+		rtrn = errors.New("Failed")
+	}
+	{{range $m := $s.Meta}}
+	if mv,ok := i.meta["{{$m.Key}}"]; ok == true {
+		if "{{$m.Val}}" != mv[0] {
+			log.Errorf("Mismatched metadata for test {{$v.Name}}, %s:%s", mv[0], "{{$m.Val}}")
+			resFile.Write([]byte(fmt.Sprintf("Mismatched metadata on server %s expected %s, got %s\n", "{{$s.Name}}", "{{$m.Val}}", mv[0])))
+			rtrn = errors.New("Failed")
+		}
+	}
+	{{end}}
+	{{end}}
+
+	return rtrn
+}
+{{end}}
+
+
diff --git a/tests/afrouter/templates/server.go b/tests/afrouter/templates/server.go
new file mode 100644
index 0000000..8461d38
--- /dev/null
+++ b/tests/afrouter/templates/server.go
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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.
+ */
+// The template for the tester.
+// This template is filled in by the
+// test driver based on the configuration.
+
+package main
+
+import (
+	"fmt"
+	"net"
+	"errors"
+	"encoding/json"
+	"google.golang.org/grpc"
+	"golang.org/x/net/context"
+	"google.golang.org/grpc/metadata"
+	"github.com/opencord/voltha-go/common/log"
+	// Values generated by the go template
+	{{range .Imports}}
+	"{{.}}"
+	{{end}}
+	{{range .ProtoImports}}
+	{{.Short}} "{{.Package}}"
+	{{end}}
+	// End go template values
+)
+
+// The channels to get fed the expected results by the test driver.
+//var {{.Name}}Meta <-chan string
+////var {{.Name}}Payload <-chan string
+
+{{if .Ct}}{{else}}
+type reply struct {
+	repl interface{}
+}
+type incoming struct {
+	meta metadata.MD
+	payload string
+}
+type  serverCtl struct {
+	replyData chan * reply
+	incmg chan * incoming
+}
+{{end}}
+
+type {{.Name}}TestServer struct {
+	control *serverCtl
+	srv *grpc.Server
+}
+
+/*
+func init() {
+	{{if .Ct}}{{else}}
+	if _, err := log.SetDefaultLogger(log.JSON, 0, nil); err != nil {
+		log.With(log.Fields{"error": err}).Fatal("Cannot setup logging")
+	}
+	{{end}}
+	{{.Name}}ListenAndServe()
+}
+*/
+
+
+func {{.Name}}ListenAndServe() error {
+	var s {{.Name}}TestServer
+
+	s.control = &serverCtl{replyData:make(chan *reply,1), incmg:make(chan *incoming,1)}
+	servers["{{.Name}}"] = s.control
+
+	log.Debugf("Starting server %s on port %d", "{{.Name}}", {{.Port}})
+	// THe test head always uses localhost 127.0.0.1
+	addr := fmt.Sprintf("127.0.0.1:%d", {{.Port}})
+
+	// Create the gRPC server
+	s.srv = grpc.NewServer()
+
+{{range .ProtoImports}}
+	// Register the handler object
+	{{.Short}}.Register{{.Service}}Server(s.srv, s)
+{{end}}
+
+	// Create the channel to listen on
+	lis, err := net.Listen("tcp", addr)
+	if err != nil {
+		log.Errorf("could not listen on %s: %s", addr, err)
+		return err
+	}
+
+	// Serve and Listen
+	go func() {
+		if err = s.srv.Serve(lis); err != nil {
+			log.Errorf("grpc serve error: %s", err)
+			return
+		}
+	}()
+
+	return err
+}
+
+{{range .Methods}}
+{{if .Ss}}
+func (ts {{$.Name}}TestServer) {{.Name}}(in *{{.Param}}, srvr {{.Pkg}}.{{.Svc}}_{{.Name}}Server) error {
+	return nil
+}
+{{else if .Cs}}
+func (ts {{$.Name}}TestServer) {{.Name}}({{.Pkg}}.{{.Svc}}_{{.Name}}Server) error {
+	return nil
+}
+{{else}}
+func  (ts {{$.Name}}TestServer) {{.Name}}(ctx context.Context, in *{{.Param}}) (*{{.Rtrn}}, error) {
+	var r * incoming = &incoming{}
+	// Read the metadata
+	if md,ok := metadata.FromIncomingContext(ctx); ok == false {
+		log.Error("Getting matadata during call to {{.Name}}")
+	} else {
+		r.meta = md.Copy()
+	}
+	// Read the data sent to the function
+	if parm,err := json.Marshal(in); err != nil {
+		log.Error("Marshalling the {{.Param}} for {{.Name}}")
+	} else {
+		r.payload = string(parm)
+	}
+	// Send the server information back to the test framework
+	ts.control.incmg <- r
+	// Read the value that needs to be returned from the channel
+	d := <-ts.control.replyData
+	switch r := d.repl.(type) {
+		case *{{.Rtrn}}:
+			return r, nil
+		default:
+			return nil, errors.New("Mismatched type in call to {{.Name}}")
+	}
+	return &{{.Rtrn}}{},nil
+}
+{{end}}
+{{end}}
+
diff --git a/tests/afrouter/templates/serverInit.go b/tests/afrouter/templates/serverInit.go
new file mode 100644
index 0000000..e97e6ff
--- /dev/null
+++ b/tests/afrouter/templates/serverInit.go
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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
+
+var servers map[string]*serverCtl
+
+func serverInit() error {
+	servers = make(map[string]*serverCtl)
+	{{range .}}
+	if err := {{.Name}}ListenAndServe(); err != nil {
+		return err
+	}
+	{{end}}
+	return nil
+}
+
+
diff --git a/tests/afrouter/tester.go b/tests/afrouter/tester.go
new file mode 100644
index 0000000..e3d883c
--- /dev/null
+++ b/tests/afrouter/tester.go
@@ -0,0 +1,591 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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.
+ */
+// gRPC affinity router with active/active backends
+
+package main
+
+// Command line parameters and parsing
+import (
+	"os"
+	"fmt"
+	"flag"
+	"path"
+	//"bufio"
+	"errors"
+	"os/exec"
+	"strconv"
+	"io/ioutil"
+	"encoding/json"
+	"text/template"
+	"github.com/golang/protobuf/proto"
+	"github.com/opencord/voltha-go/common/log"
+	pb "github.com/golang/protobuf/protoc-gen-go/descriptor"
+)
+
+type TestConfig struct {
+	configFile *string
+	logLevel *int
+	grpcLog *bool
+	Suites []string `json:"suites"`
+}
+
+
+type Connection struct {
+	Name string `json:"name"`
+	Port string `json:"port"`
+}
+
+type ProtoFile struct {
+	ImportPath string `json:"importPath"`
+	Service string `json:"service"`
+	Package string `json:"package"`
+}
+
+type ProtoSubst struct {
+	From string `json:"from"`
+	To string `json:"to"`
+}
+
+type Environment struct {
+	Command string `json:"cmdLine"`
+	ProtoFiles []ProtoFile `json:"protoFiles"`
+	ProtoDesc string `json:"protoDesc"`
+	Clients []Connection `json:"clients"`
+	Servers []Connection `json:"servers"`
+	Imports []string `json:"imports"`
+	ProtoSubsts []ProtoSubst `json:"protoSubst"`
+}
+
+type Rpc struct {
+	Client string `json:"client"`
+	Method string `json:"method"`
+	Param string `json:"param"`
+	Expect string `json:"expect"`
+}
+
+type MetaD struct {
+	Key string `json:"key"`
+	Val string `json:"value"`
+}
+
+type Server struct {
+	Name string `json:"name"`
+	Meta []MetaD `json:"meta"`
+}
+
+type Test struct {
+	Name string `json:"name"`
+	Send Rpc `json:"send"`
+	Servers []Server `json:"servers"`
+}
+
+type TestSuite struct {
+	Env Environment `json:"environment"`
+	Tests []Test `json:"tests"`
+}
+
+type ProtoImport struct {
+	Service string
+	Short string
+	Package string
+}
+
+type SendItem struct {
+	Client string
+	Method string
+	Param string
+	ParamType string
+	Expect string
+	ExpectType string
+}
+
+type TestCase struct {
+	Name string
+	Send SendItem
+	Srvr []Server
+}
+
+type TestList struct {
+	ProtoImports []ProtoImport
+	Imports []string
+	Tests []TestCase
+}
+
+type ClientConfig struct {
+	Ct int
+	Name string
+	Port string
+	Imports []string
+	Methods map[string]*mthd
+	ProtoImports []ProtoImport
+}
+
+type ServerConfig struct {
+	Ct int
+	Name string
+	Port string
+	Imports []string
+	Methods map[string]*mthd
+	ProtoImports []ProtoImport
+}
+
+type mthd struct {
+	Pkg string
+	Svc string
+	Name string
+	Param string
+	Rtrn string
+	Ss bool // Server streaming 
+	Cs bool // Clieent streaming 
+}
+
+
+func parseCmd() (*TestConfig, error) {
+	config := &TestConfig{}
+	cmdParse := flag.NewFlagSet(path.Base(os.Args[0]), flag.ContinueOnError);
+	config.configFile = cmdParse.String("config", "suites.json", "The configuration file for the affinity router tester")
+	config.logLevel = cmdParse.Int("logLevel", 0, "The log level for the affinity router tester")
+	config.grpcLog = cmdParse.Bool("grpclog", false, "Enable GRPC logging")
+
+	err := cmdParse.Parse(os.Args[1:]);
+	if err != nil {
+		//return err
+		return nil, errors.New("Error parsing the command line");
+	}
+	return config, nil
+}
+
+func (conf * TestConfig) loadConfig() error {
+
+	configF, err := os.Open(*conf.configFile);
+	log.Info("Loading configuration from: ", *conf.configFile)
+	if err != nil {
+		log.Error(err)
+		return err
+	}
+
+	defer configF.Close()
+
+	if configBytes, err := ioutil.ReadAll(configF); err != nil {
+		log.Error(err)
+		return err
+	} else if err := json.Unmarshal(configBytes, conf); err != nil {
+		log.Error(err)
+		return err
+	}
+
+	return nil
+}
+
+func (suite * TestSuite) loadSuite(fileN string)  error {
+	suiteF, err := os.Open(fileN);
+	log.Info("Loading test suite from: ", fileN)
+	if err != nil {
+		log.Error(err)
+		return err
+	}
+
+	defer suiteF.Close()
+
+	if suiteBytes, err := ioutil.ReadAll(suiteF); err != nil {
+		log.Error(err)
+		return err
+	} else if err := json.Unmarshal(suiteBytes, suite); err != nil {
+		log.Error(err)
+		return err
+	}
+
+	return nil
+}
+
+func loadProtoMap(fileName string, pkg string, svc string, substs []ProtoSubst) (map[string]*mthd, error) {
+	var mthds map[string]*mthd = make(map[string]*mthd)
+	var rtrn_err bool
+
+	// Load the protobuf descriptor file
+	protoDescriptor := &pb.FileDescriptorSet{}
+	fb, err := ioutil.ReadFile(fileName);
+	if err != nil {
+		log.Errorf("Could not open proto file '%s'",fileName)
+		rtrn_err = true
+	}
+	err = proto.Unmarshal(fb, protoDescriptor)
+	if err != nil {
+		log.Errorf("Could not unmarshal %s, %v", "proto.pb", err)
+		rtrn_err = true
+	}
+
+	var substM  map[string]string = make(map[string]string)
+	// Create a substitution map
+	log.Debugf("Creating import map")
+	for _,v := range substs {
+		log.Debugf("Mapping from %s to %s", v.From, v.To)
+		substM[v.From] = v.To
+	}
+
+
+	// Build the a map containing the method as the key
+	// and the paramter and return types as the fields
+	for _,f := range protoDescriptor.File {
+		if *f.Package == pkg {
+			for _, s:= range f.Service {
+				if *s.Name == svc {
+					log.Debugf("Loading package data '%s' for service '%s'", *f.Package, *s.Name)
+					// Now create a map keyed by method name with the value being the
+					// field number of the route selector.
+					//var ok bool
+					for _,m := range s.Method {
+						// Find the input type in the messages and extract the
+						// field number and save it for future reference.
+						log.Debugf("Processing method (%s(%s) (%s){}",*m.Name, (*m.InputType)[1:], (*m.OutputType)[1:])
+						mthds[*m.Name] = &mthd{Pkg:pkg, Svc:svc, Name:*m.Name, Param:(*m.InputType)[1:],
+												Rtrn:(*m.OutputType)[1:]}
+						if m.ClientStreaming != nil && *m.ClientStreaming == true {
+							log.Debugf("Method %s is a client streaming method", *m.Name)
+							mthds[*m.Name].Cs = true
+						}
+						if m.ServerStreaming != nil && *m.ServerStreaming == true {
+							log.Debugf("Method %s is a server streaming method", *m.Name)
+							mthds[*m.Name].Ss = true
+						}
+						// Perform the required substitutions
+						if _,ok := substM[mthds[*m.Name].Param]; ok == true {
+							mthds[*m.Name].Param = substM[mthds[*m.Name].Param]
+						}
+						if _,ok := substM[mthds[*m.Name].Rtrn]; ok == true {
+							mthds[*m.Name].Rtrn = substM[mthds[*m.Name].Rtrn]
+						}
+					}
+				}
+			}
+		}
+	}
+	if rtrn_err {
+		return nil,errors.New(fmt.Sprintf("Failed to load protobuf descriptor file '%s'",fileName))
+	}
+	return mthds, nil
+}
+
+// Server source code generation
+func generateServers(conf *TestConfig, suiteDir string, ts * TestSuite,
+						t *template.Template) error {
+	var servers []ServerConfig
+
+	for k,v := range ts.Env.Servers {
+		log.Infof("Generating the code for server[%d]: %s", k, v.Name)
+		sc := &ServerConfig{Name:v.Name, Port:v.Port, Ct:k, Imports:ts.Env.Imports}
+		for k1,v1 := range ts.Env.ProtoFiles {
+			imp := &ProtoImport{Short:"pb"+strconv.Itoa(k1),
+								Package:v1.ImportPath+v1.Package,
+								Service:v1.Service}
+			imp = &ProtoImport{Short:v1.Package,
+								Package:v1.ImportPath+v1.Package,
+								Service:v1.Service}
+			sc.ProtoImports = append(sc.ProtoImports, *imp)
+			// Compile the template from the file
+			log.Debugf("Proto substs: %v", ts.Env.ProtoSubsts)
+			if mthds, err := loadProtoMap(ts.Env.ProtoDesc, v1.Package,
+											v1.Service, ts.Env.ProtoSubsts); err != nil {
+				log.Errorf("Unable to process proto descriptor file %s for package: %s, service: %s",
+						ts.Env.ProtoDesc, v1.Package, v1.Service)
+				return err
+			} else {
+				//Generate all the function calls required by the 
+				sc.Methods = mthds
+			}
+		}
+		log.Debugf("Server: %v", *sc)
+		// Save this server for the next steop
+		servers = append(servers, *sc)
+		// Open an output file to put the output in.
+		if f,err := os.Create(suiteDir+"/"+v.Name+".go"); err == nil {
+			defer f.Close()
+			//if err := t.ExecuteTemplate(os.Stdout, "server.go", *sc); err != nil {}
+			if err := t.ExecuteTemplate(f, "server.go", *sc); err != nil {
+				log.Errorf("Unable to execute template for server[%d]: %s: %v", k, v.Name, err)
+				return err
+			}
+		}
+	}
+	// Generate the server initialization code
+	if f,err := os.Create(suiteDir+"/serverInit.go"); err == nil {
+		defer f.Close()
+		//if err := t.ExecuteTemplate(os.Stdout, "server.go", *sc); err != nil {}
+		if err := t.ExecuteTemplate(f, "serverInit.go", servers); err != nil {
+			log.Errorf("Unable to execute template for serverInit.go: %v", err)
+			return err
+		}
+	}
+
+	return nil
+}
+
+func generateClients(conf *TestConfig, suiteDir string, ts * TestSuite,
+						t *template.Template) error {
+	var clients []ClientConfig
+	for k,v := range ts.Env.Clients {
+		log.Infof("Generating the code for client[%d]: %s", k, v.Name)
+		cc := &ClientConfig{Name:v.Name, Port:v.Port, Ct:k, Imports:ts.Env.Imports}
+		// TODO: This loop makes no sense, the only proto map that would remain
+		// after this loop is the last one loaded. Fix this to load the map
+		// for all services not just the last one.
+		for _,v1 := range ts.Env.ProtoFiles {
+			imp := &ProtoImport{Short:v1.Package,
+								Package:v1.ImportPath+v1.Package,
+								Service:v1.Service}
+			cc.ProtoImports = append(cc.ProtoImports, *imp)
+			// Compile the template from the file
+			log.Debugf("Proto substs: %v", ts.Env.ProtoSubsts)
+			if mthds, err := loadProtoMap(ts.Env.ProtoDesc, v1.Package,
+											v1.Service, ts.Env.ProtoSubsts); err != nil {
+				log.Errorf("Unable to process proto descriptor file %s for package: %s, service: %s",
+						ts.Env.ProtoDesc, v1.Package, v1.Service)
+				return err
+			} else {
+				//Generate all the function calls required by the 
+				cc.Methods = mthds
+			}
+		}
+		clients = append(clients, *cc)
+		if f,err := os.Create(suiteDir+"/"+v.Name+".go"); err == nil {
+			_=f
+			defer f.Close()
+			if err := t.ExecuteTemplate(f, "client.go", cc); err != nil {
+				log.Errorf("Unable to execute template for client.go: %v", err)
+				return err
+			}
+		} else {
+			log.Errorf("Couldn't create file %s : %v", suiteDir+"/client.go", err)
+			return err
+		}
+	}
+	if f,err := os.Create(suiteDir+"/clientInit.go"); err == nil {
+		defer f.Close()
+		//if err := t.ExecuteTemplate(os.Stdout, "server.go", *sc); err != nil {}
+		if err := t.ExecuteTemplate(f, "clientInit.go", clients); err != nil {
+			log.Errorf("Unable to execute template for clientInit.go: %v", err)
+			return err
+		}
+	}
+	return nil
+}
+
+func serverExists(srvr string, ts * TestSuite) bool {
+	for _,v := range ts.Env.Servers {
+		if v.Name == srvr {
+			return true
+		}
+	}
+	return false
+}
+func generateTestCases(conf *TestConfig, suiteDir string, ts * TestSuite,
+						t *template.Template) error {
+	var mthdMap map[string]*mthd
+	// Generate the test cases
+	log.Info("Generating the test cases: runTests.go")
+	tc := &TestList{Imports:ts.Env.Imports}
+
+	// Load the proto descriptor file
+	// TODO: This loop makes no sense, the only proto map that would remain
+	// after this loop is the last one loaded. Fix this to load the map
+	// for all services not just the last one.
+	for _,v := range ts.Env.ProtoFiles {
+		imp := &ProtoImport{Short:v.Package,
+							Package:v.ImportPath+v.Package,
+							Service:v.Service}
+		tc.ProtoImports = append(tc.ProtoImports, *imp)
+		// Compile the template from the file
+		log.Debugf("Proto substs: %v", ts.Env.ProtoSubsts)
+		if mthds, err := loadProtoMap(ts.Env.ProtoDesc, v.Package,
+										v.Service, ts.Env.ProtoSubsts); err != nil {
+			log.Errorf("Unable to process proto descriptor file %s for package: %s, service: %s",
+					ts.Env.ProtoDesc, v.Package, v.Service)
+			return err
+		} else {
+			mthdMap = mthds
+		}
+	}
+	// Create the test data structure for the template
+	for _,v := range ts.Tests {
+		var test TestCase
+
+		test.Name = v.Name
+		test.Send.Client = v.Send.Client
+		test.Send.Method = v.Send.Method
+		test.Send.Param = v.Send.Param
+		test.Send.ParamType = mthdMap[test.Send.Method].Param
+		test.Send.Expect = v.Send.Expect
+		test.Send.ExpectType = mthdMap[test.Send.Method].Rtrn
+		for _,v1 := range v.Servers {
+			var srvr Server
+			if serverExists(v1.Name, ts) == false {
+				log.Errorf("Server '%s' is not defined!!", v1.Name)
+				return errors.New(fmt.Sprintf("Failed to build test case %s", v.Name))
+			}
+			srvr.Name = v1.Name
+			srvr.Meta = v1.Meta
+			test.Srvr = append(test.Srvr, srvr)
+		}
+		tc.Tests = append(tc.Tests, test)
+	}
+	if f,err := os.Create(suiteDir+"/runTests.go"); err == nil {
+		if err := t.ExecuteTemplate(f, "runTests.go", tc); err != nil {
+				log.Errorf("Unable to execute template for runTests.go: %v", err)
+		}
+		f.Close()
+	} else {
+		log.Errorf("Couldn't create file %s : %v", suiteDir+"/runTests.go", err)
+	}
+	return nil
+}
+
+func generateTestSuites(conf *TestConfig, srcDir string, outDir string) error {
+
+	// Create a directory for the tests
+	if err := os.Mkdir(srcDir, 0777); err != nil {
+		log.Errorf("Unable to create directory 'tests':%v\n", err)
+		return err
+	}
+
+	for k,v := range conf.Suites {
+	    var suiteDir string = srcDir+"/"+v
+		log.Debugf("Suite[%d] - %s", k, v)
+		ts := &TestSuite{}
+		ts.loadSuite(v)
+		log.Debugf("Suite %s: %v", v, ts)
+		log.Info("Processing test suite %s", v)
+
+		t := template.Must(template.New("").ParseFiles("../templates/server.go",
+														"../templates/serverInit.go",
+														"../templates/client.go",
+														"../templates/clientInit.go",
+														"../templates/runTests.go",
+														"../templates/main.go"))
+		// Create a directory for he source code for this test suite
+		if err := os.Mkdir(suiteDir, 0777); err != nil {
+			log.Errorf("Unable to create directory '%s':%v\n", v, err)
+			return err
+		}
+		// Generate the server source files
+		if err := generateServers(conf, suiteDir, ts, t); err != nil {
+			log.Errorf("Unable to generate server source files: %v", err)
+			return err
+		}
+		// Generate the client source files
+		if err := generateClients(conf, suiteDir, ts, t); err != nil {
+			log.Errorf("Unable to generate client source files: %v", err)
+			return err
+		}
+		// Generate the test case source file
+		if err := generateTestCases(conf, suiteDir, ts, t); err != nil {
+			log.Errorf("Unable to generate test case source file: %v", err)
+			return err
+		}
+
+		// Finally generate the main file
+		log.Info("Generating main.go")
+		if f,err := os.Create(suiteDir+"/main.go"); err == nil {
+			if err := t.ExecuteTemplate(f, "main.go", ts.Env); err != nil {
+					log.Errorf("Unable to execute template for main.go: %v", err)
+			}
+			f.Close()
+		} else {
+			log.Errorf("Couldn't create file %s : %v", suiteDir+"/main.go", err)
+		}
+
+		log.Infof("Compiling test suite: %s in directory %s", v, suiteDir)
+		if err := os.Chdir(suiteDir); err != nil {
+			log.Errorf("Could not change to directory '%s':%v",suiteDir, err)
+		}
+		cmd := exec.Command("go", "build", "-o", outDir+"/"+v[:len(v)-5])
+		cmd.Stdin = os.Stdin
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+		if err := cmd.Run(); err != nil {
+			log.Errorf("Error running the compile command:%v", err)
+		}
+		if err := os.Chdir("../../suites"); err != nil {
+			log.Errorf("Could not change to directory '%s':%v","../../suites", err)
+		}
+
+		// Now generate the test cases
+
+	}
+	return nil
+
+}
+
+func generateTestDriver(conf *TestConfig, srcDir string, outDir string) error {
+	// Generate the main test driver file
+	if err := os.Mkdir(srcDir, 0777); err != nil {
+		log.Errorf("Unable to create directory 'driver':%v\n", err)
+		return err
+	}
+	t := template.Must(template.New("").ParseFiles("../templates/runAll.go"))
+	if f,err := os.Create(srcDir+"/runAll.go"); err == nil {
+		if err := t.ExecuteTemplate(f, "runAll.go", conf.Suites); err != nil {
+				log.Errorf("Unable to execute template for main.go: %v", err)
+		}
+		f.Close()
+	} else {
+		log.Errorf("Couldn't create file %s : %v", srcDir+"/runAll.go", err)
+	}
+
+	// Compile the test driver file
+	log.Info("Compiling the test driver")
+	if err := os.Chdir("../tests/driver"); err != nil {
+		log.Errorf("Could not change to directory 'driver':%v", err)
+	}
+	cmd := exec.Command("go", "build", "-o", outDir+"/runAll")
+	cmd.Stdin = os.Stdin
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	if err := cmd.Run(); err != nil {
+		log.Errorf("Error running the compile command:%v", err)
+	}
+	if err := os.Chdir("../../suites"); err != nil {
+		log.Errorf("Could not change to directory 'driver':%v",err)
+	}
+
+	return nil
+}
+
+
+func main() {
+
+	conf,err := parseCmd()
+	if err != nil {
+		fmt.Printf("Error: %v\n", err)
+		return
+	}
+
+	// Setup logging
+	if _, err := log.SetDefaultLogger(log.JSON, *conf.logLevel, nil); err != nil {
+		log.With(log.Fields{"error": err}).Fatal("Cannot setup logging")
+	}
+
+	defer log.CleanUp()
+
+	// Parse the config file
+	if err := conf.loadConfig(); err != nil {
+		log.Error(err)
+	}
+
+	generateTestSuites(conf, "../tests", "/src/tests")
+	generateTestDriver(conf, "../tests/driver", "/src/tests")
+	return
+}
+
diff --git a/tests/afrouter/tester/main.go b/tests/afrouter/tester/main.go
new file mode 100644
index 0000000..8190fe6
--- /dev/null
+++ b/tests/afrouter/tester/main.go
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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.
+ */
+// The template for the tester.
+// This template is filled in by the
+// test driver based on the configuration.
+
+package main
+
+import (
+	"github.com/opencord/voltha-go/common/log"
+)
+
+func main() {
+	// Setup logging
+	/*
+	if _, err := log.SetDefaultLogger(log.JSON, 0, nil); err != nil {
+		log.With(log.Fields{"error": err}).Fatal("Cannot setup logging")
+	}
+	*/
+
+	defer log.CleanUp()
+
+	log.Info("Template runs!")
+}