Repository: kserve/kserve

Issue:
Add support for multi CRD versions and conversion webhook between v1alpha2 and v1beta1
- Generate CRDs for both v1alpha2 and v1beta1
- Use v1beta1 as hub version and setup conversion webook to convert from/to v1alpha2.


Discussion:
Issue-Label Bot is automatically applying the labels:

| Label  | Probability |
| ------------- | ------------- |
| area/inference | 0.50 |
| kind/feature | 0.98 |

Please mark this comment with :thumbsup: or :thumbsdown: to give our bot feedback! 
Links: [app homepage](https://github.com/marketplace/issue-label-bot), [dashboard](https://github.com/marketplace/issue-label-botdata/kubeflow/kfserving) and [code](https://github.com/hamelsmu/MLapp) for this bot.

Resolving patch:
diff --git a/python/kfserving/kfserving/api/kf_serving_client.py b/python/kfserving/kfserving/api/kf_serving_client.py
--- a/python/kfserving/kfserving/api/kf_serving_client.py
+++ b/python/kfserving/kfserving/api/kf_serving_client.py
@@ -85,13 +85,14 @@ def set_credentials(self, storage_type, namespace=None, credentials_file=None,
             raise RuntimeError("Invalid storage_type: %s, only support GCS, S3 and Azure\
                 currently.\n" % storage_type)
 
-    def create(self, inferenceservice, namespace=None, watch=False, timeout_seconds=600): #pylint:disable=inconsistent-return-statements
+    def create(self, inferenceservice, namespace=None, watch=False, timeout_seconds=600, version=constants.KFSERVING_VERSION): #pylint:disable=inconsistent-return-statements
         """
         Create the inference service
         :param inferenceservice: inference service object
         :param namespace: defaults to current or default namespace
         :param watch: True to watch the created service until timeout elapsed or status is ready
         :param timeout_seconds: timeout seconds for watch, default to 600s
+        :param version: api group version
         :return: created inference service
         """
 
@@ -101,7 +102,7 @@ def create(self, inferenceservice, namespace=None, watch=False, timeout_seconds=
         try:
             outputs = self.api_instance.create_namespaced_custom_object(
                 constants.KFSERVING_GROUP,
-                constants.KFSERVING_VERSION,
+                version,
                 namespace,
                 constants.KFSERVING_PLURAL,
                 inferenceservice)
@@ -118,13 +119,14 @@ def create(self, inferenceservice, namespace=None, watch=False, timeout_seconds=
         else:
             return outputs
 
-    def get(self, name=None, namespace=None, watch=False, timeout_seconds=600): #pylint:disable=inconsistent-return-statements
+    def get(self, name=None, namespace=None, watch=False, timeout_seconds=600, version=constants.KFSERVING_VERSION): #pylint:disable=inconsistent-return-statements
         """
         Get the inference service
         :param name: existing inference service name
         :param namespace: defaults to current or default namespace
         :param watch: True to watch the service until timeout elapsed or status is ready
         :param timeout_seconds: timeout seconds for watch, default to 600s
+        :param version: api group version
         :return: inference service
         """
         if namespace is None:
@@ -140,7 +142,7 @@ def get(self, name=None, namespace=None, watch=False, timeout_seconds=600): #pyl
                 try:
                     return self.api_instance.get_namespaced_custom_object(
                         constants.KFSERVING_GROUP,
-                        constants.KFSERVING_VERSION,
+                        version,
                         namespace,
                         constants.KFSERVING_PLURAL,
                         name)
@@ -157,7 +159,7 @@ def get(self, name=None, namespace=None, watch=False, timeout_seconds=600): #pyl
                 try:
                     return self.api_instance.list_namespaced_custom_object(
                         constants.KFSERVING_GROUP,
-                        constants.KFSERVING_VERSION,
+                        version,
                         namespace,
                         constants.KFSERVING_PLURAL)
                 except client.rest.ApiException as e:
@@ -165,7 +167,7 @@ def get(self, name=None, namespace=None, watch=False, timeout_seconds=600): #pyl
                         "Exception when calling CustomObjectsApi->list_namespaced_custom_object:\
                         %s\n" % e)
 
-    def patch(self, name, inferenceservice, namespace=None, watch=False, timeout_seconds=600): # pylint:disable=too-many-arguments,inconsistent-return-statements
+    def patch(self, name, inferenceservice, namespace=None, watch=False, timeout_seconds=600, version=constants.KFSERVING_VERSION): # pylint:disable=too-many-arguments,inconsistent-return-statements
         """
         Patch existing inference service
         :param name: existing inference service name
@@ -173,6 +175,7 @@ def patch(self, name, inferenceservice, namespace=None, watch=False, timeout_sec
         :param namespace: defaults to current or default namespace
         :param watch: True to watch the patched service until timeout elapsed or status is ready
         :param timeout_seconds: timeout seconds for watch, default to 600s
+        :param version: api group version
         :return: patched inference service
         """
         if namespace is None:
@@ -181,7 +184,7 @@ def patch(self, name, inferenceservice, namespace=None, watch=False, timeout_sec
         try:
             outputs = self.api_instance.patch_namespaced_custom_object(
                 constants.KFSERVING_GROUP,
-                constants.KFSERVING_VERSION,
+                version,
                 namespace,
                 constants.KFSERVING_PLURAL,
                 name,
@@ -201,7 +204,7 @@ def patch(self, name, inferenceservice, namespace=None, watch=False, timeout_sec
         else:
             return outputs
 
-    def replace(self, name, inferenceservice, namespace=None, watch=False, timeout_seconds=600): # pylint:disable=too-many-arguments,inconsistent-return-statements
+    def replace(self, name, inferenceservice, namespace=None, watch=False, timeout_seconds=600, version=constants.KFSERVING_VERSION): # pylint:disable=too-many-arguments,inconsistent-return-statements
         """
         Replace the existing inference service
         :param name: existing inference service name
@@ -209,6 +212,7 @@ def replace(self, name, inferenceservice, namespace=None, watch=False, timeout_s
         :param namespace: defaults to current or default namespace
         :param watch: True to watch the replaced service until timeout elapsed or status is ready
         :param timeout_seconds: timeout seconds for watch, default to 600s
+        :param version: api group version
         :return: replaced inference service
         """
 
@@ -223,7 +227,7 @@ def replace(self, name, inferenceservice, namespace=None, watch=False, timeout_s
         try:
             outputs = self.api_instance.replace_namespaced_custom_object(
                 constants.KFSERVING_GROUP,
-                constants.KFSERVING_VERSION,
+                version,
                 namespace,
                 constants.KFSERVING_PLURAL,
                 name,
@@ -242,7 +246,7 @@ def replace(self, name, inferenceservice, namespace=None, watch=False, timeout_s
             return outputs
 
     def rollout_canary(self, name, percent, namespace=None, # pylint:disable=too-many-arguments,inconsistent-return-statements
-                       canary=None, watch=False, timeout_seconds=600):
+                       canary=None, watch=False, timeout_seconds=600, version=constants.KFSERVING_VERSION):
         """
         Rollout the canary endpoint
         :param name: inference service name
@@ -251,6 +255,7 @@ def rollout_canary(self, name, percent, namespace=None, # pylint:disable=too-man
         :param canary: canary endpoint spec
         :param watch: True to watch the service until timeout elapsed or status is ready
         :param timeout_seconds: timeout seconds for watch, default to 600s
+        :param version: api group version
         :return: inference service with canary endpoint
         """
 
@@ -264,57 +269,18 @@ def rollout_canary(self, name, percent, namespace=None, # pylint:disable=too-man
 
         current_isvc['spec']['canaryTrafficPercent'] = percent
         if canary:
+            current_isvc['spec']['default'] = canary
             current_isvc['spec']['canary'] = canary
 
         return self.patch(name=name, inferenceservice=current_isvc, namespace=namespace,
-                          watch=watch, timeout_seconds=timeout_seconds)
+                          watch=watch, timeout_seconds=timeout_seconds, version=version)
 
-    def promote(self, name, namespace=None, watch=False, timeout_seconds=600): # pylint:disable=too-many-arguments,inconsistent-return-statements
-        """
-        Promote canary endpoint to default
-        :param name: inference service name
-        :param namespace: defaults to current or default namespace
-        :param watch: True to watch the service until timeout elapsed or status is ready
-        :param timeout_seconds: timeout seconds for watch, default to 600s
-        :return: promoted inference service
-        """
-
-        if namespace is None:
-            namespace = utils.get_default_target_namespace()
-
-        current_isvc = self.get(name, namespace=namespace)
-        api_version = current_isvc['apiVersion']
-
-        try:
-            current_canary_spec = current_isvc['spec']['canary']
-        except KeyError:
-            raise RuntimeError("Cannot promote a InferenceService that has no Canary Spec.")
-
-        try:
-            annotations = current_isvc['metadata']['annotations']
-        except KeyError:
-            annotations = dict()
-
-        inferenceservice = V1alpha2InferenceService(
-            api_version=api_version,
-            kind=constants.KFSERVING_KIND,
-            metadata=client.V1ObjectMeta(
-                name=name,
-                namespace=namespace,
-                annotations=annotations),
-            spec=V1alpha2InferenceServiceSpec(
-                default=current_canary_spec,
-                canary=None,
-                canary_traffic_percent=0))
-
-        return self.replace(name=name, inferenceservice=inferenceservice, namespace=namespace,
-                            watch=watch, timeout_seconds=timeout_seconds)
-
-    def delete(self, name, namespace=None):
+    def delete(self, name, namespace=None, version=constants.KFSERVING_VERSION):
         """
         Delete the inference service
         :param name: inference service name
         :param namespace: defaults to current or default namespace
+        :param version: api group version
         :return:
         """
         if namespace is None:
@@ -323,7 +289,7 @@ def delete(self, name, namespace=None):
         try:
             return self.api_instance.delete_namespaced_custom_object(
                 constants.KFSERVING_GROUP,
-                constants.KFSERVING_VERSION,
+                version,
                 namespace,
                 constants.KFSERVING_PLURAL,
                 name,
@@ -341,7 +307,7 @@ def is_isvc_ready(self, name, namespace=None): #pylint:disable=inconsistent-retu
         :param namespace: defaults to current or default namespace
         :return:
         """
-        kfsvc_status = self.get(name, namespace=namespace)
+        kfsvc_status = self.get(name, namespace=namespace, version=constants.KFSERVING_V1BETA1_VERSION)
         status = 'Unknown'
         for condition in kfsvc_status['status'].get('conditions', {}):
             if condition.get('type', '') == 'Ready':
@@ -375,6 +341,6 @@ def wait_isvc_ready(self, name, namespace=None, #pylint:disable=too-many-argumen
                 if self.is_isvc_ready(name, namespace=namespace):
                     return
 
-            current_isvc = self.get(name, namespace=namespace)
+            current_isvc = self.get(name, namespace=namespace, version=constants.KFSERVING_V1BETA1_VERSION)
             raise RuntimeError("Timeout to start the InferenceService {}. \
                                The InferenceService is as following: {}".format(name, current_isvc))
diff --git a/python/kfserving/kfserving/constants/constants.py b/python/kfserving/kfserving/constants/constants.py
--- a/python/kfserving/kfserving/constants/constants.py
+++ b/python/kfserving/kfserving/constants/constants.py
@@ -19,6 +19,7 @@
 KFSERVING_KIND = 'InferenceService'
 KFSERVING_PLURAL = 'inferenceservices'
 KFSERVING_VERSION = os.environ.get('KFSERVING_VERSION', 'v1alpha2')
+KFSERVING_V1BETA1_VERSION = 'v1beta1'
 KFSERVING_API_VERSION = KFSERVING_GROUP + '/' + KFSERVING_VERSION
 
 KFSERVING_LOGLEVEL = os.environ.get('KFSERVING_LOGLEVEL', 'INFO').upper()


Test patch:
diff --git a/config/overlays/test/configmap/inferenceservice.yaml b/config/overlays/test/configmap/inferenceservice.yaml
--- a/config/overlays/test/configmap/inferenceservice.yaml
+++ b/config/overlays/test/configmap/inferenceservice.yaml
@@ -30,7 +30,7 @@ data:
         },
         "triton": {
             "image": "nvcr.io/nvidia/tritonserver",
-            "defaultImageVersion": "20.03-py3"
+            "defaultImageVersion": "20.08-py3"
         }
     }
   transformers: |-
diff --git a/pkg/apis/serving/v1alpha2/inferenceservice_conversion_test.go b/pkg/apis/serving/v1alpha2/inferenceservice_conversion_test.go
new file mode 100644
--- /dev/null
+++ b/pkg/apis/serving/v1alpha2/inferenceservice_conversion_test.go
@@ -0,0 +1,283 @@
+package v1alpha2
+
+import (
+	"github.com/golang/protobuf/proto"
+	"github.com/google/go-cmp/cmp"
+	"github.com/kubeflow/kfserving/pkg/apis/serving/v1beta1"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"testing"
+)
+
+func TestInferenceServiceConversion(t *testing.T) {
+	scenarios := map[string]struct {
+		v1alpha2spec *InferenceService
+		v1beta1Spec  *v1beta1.InferenceService
+	}{
+		"customPredictor": {
+			v1alpha2spec: &InferenceService{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "custom-predictor",
+					Namespace: "default",
+				},
+				Spec: InferenceServiceSpec{
+					Default: EndpointSpec{
+						Predictor: PredictorSpec{
+							DeploymentSpec: DeploymentSpec{
+								MinReplicas: GetIntReference(1),
+								MaxReplicas: 3,
+								Parallelism: 1,
+							},
+							Custom: &CustomSpec{
+								Container: v1.Container{
+									Name:  "kfserving-container",
+									Image: "custom-predictor:v1",
+									Resources: v1.ResourceRequirements{
+										Requests: v1.ResourceList{
+											v1.ResourceCPU:    resource.MustParse("1"),
+											v1.ResourceMemory: resource.MustParse("2Gi"),
+										},
+										Limits: v1.ResourceList{
+											v1.ResourceCPU:    resource.MustParse("1"),
+											v1.ResourceMemory: resource.MustParse("2Gi"),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			v1beta1Spec: &v1beta1.InferenceService{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "custom-predictor",
+					Namespace: "default",
+				},
+				Spec: v1beta1.InferenceServiceSpec{
+					Predictor: v1beta1.PredictorSpec{
+						ComponentExtensionSpec: v1beta1.ComponentExtensionSpec{
+							MinReplicas:          GetIntReference(1),
+							MaxReplicas:          3,
+							ContainerConcurrency: proto.Int64(1),
+						},
+						PodSpec: v1beta1.PodSpec{
+							Containers: []v1.Container{
+								{
+									Name:  "kfserving-container",
+									Image: "custom-predictor:v1",
+									Resources: v1.ResourceRequirements{
+										Requests: v1.ResourceList{
+											v1.ResourceCPU:    resource.MustParse("1"),
+											v1.ResourceMemory: resource.MustParse("2Gi"),
+										},
+										Limits: v1.ResourceList{
+											v1.ResourceCPU:    resource.MustParse("1"),
+											v1.ResourceMemory: resource.MustParse("2Gi"),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"transformer": {
+			v1alpha2spec: &InferenceService{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "conversionTest",
+					Namespace: "default",
+				},
+				Spec: InferenceServiceSpec{
+					Default: EndpointSpec{
+						Predictor: PredictorSpec{
+							DeploymentSpec: DeploymentSpec{
+								MinReplicas: GetIntReference(1),
+								MaxReplicas: 3,
+								Parallelism: 1,
+							},
+							Tensorflow: &TensorflowSpec{
+								StorageURI:     "s3://test/mnist/export",
+								RuntimeVersion: "1.13.0",
+							},
+						},
+						Transformer: &TransformerSpec{
+							DeploymentSpec: DeploymentSpec{
+								MinReplicas: GetIntReference(1),
+								MaxReplicas: 3,
+								Parallelism: 1,
+							},
+							Custom: &CustomSpec{
+								Container: v1.Container{
+									Name:  "kfserving-container",
+									Image: "transformer:v1",
+									Resources: v1.ResourceRequirements{
+										Requests: v1.ResourceList{
+											v1.ResourceCPU:    resource.MustParse("1"),
+											v1.ResourceMemory: resource.MustParse("2Gi"),
+										},
+										Limits: v1.ResourceList{
+											v1.ResourceCPU:    resource.MustParse("1"),
+											v1.ResourceMemory: resource.MustParse("2Gi"),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			v1beta1Spec: &v1beta1.InferenceService{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "conversionTest",
+					Namespace: "default",
+				},
+				Spec: v1beta1.InferenceServiceSpec{
+					Predictor: v1beta1.PredictorSpec{
+						ComponentExtensionSpec: v1beta1.ComponentExtensionSpec{
+							MinReplicas:          GetIntReference(1),
+							MaxReplicas:          3,
+							ContainerConcurrency: proto.Int64(1),
+						},
+						Tensorflow: &v1beta1.TFServingSpec{
+							PredictorExtensionSpec: v1beta1.PredictorExtensionSpec{
+								StorageURI:     proto.String("s3://test/mnist/export"),
+								RuntimeVersion: proto.String("1.13.0"),
+							},
+						},
+					},
+					Transformer: &v1beta1.TransformerSpec{
+						ComponentExtensionSpec: v1beta1.ComponentExtensionSpec{
+							MinReplicas:          GetIntReference(1),
+							MaxReplicas:          3,
+							ContainerConcurrency: proto.Int64(1),
+						},
+						PodSpec: v1beta1.PodSpec{
+							Containers: []v1.Container{
+								{
+									Name:  "kfserving-container",
+									Image: "transformer:v1",
+									Resources: v1.ResourceRequirements{
+										Requests: v1.ResourceList{
+											v1.ResourceCPU:    resource.MustParse("1"),
+											v1.ResourceMemory: resource.MustParse("2Gi"),
+										},
+										Limits: v1.ResourceList{
+											v1.ResourceCPU:    resource.MustParse("1"),
+											v1.ResourceMemory: resource.MustParse("2Gi"),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"explainer": {
+			v1alpha2spec: &InferenceService{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "explainerConversionTest",
+					Namespace: "default",
+				},
+				Spec: InferenceServiceSpec{
+					Default: EndpointSpec{
+						Predictor: PredictorSpec{
+							DeploymentSpec: DeploymentSpec{
+								MinReplicas: GetIntReference(1),
+								MaxReplicas: 3,
+								Parallelism: 1,
+							},
+							Tensorflow: &TensorflowSpec{
+								StorageURI:     "s3://test/mnist/export",
+								RuntimeVersion: "1.13.0",
+							},
+						},
+						Explainer: &ExplainerSpec{
+							DeploymentSpec: DeploymentSpec{
+								MinReplicas: GetIntReference(1),
+								MaxReplicas: 3,
+								Parallelism: 1,
+							},
+							Alibi: &AlibiExplainerSpec{
+								Type:           AlibiAnchorsTabularExplainer,
+								StorageURI:     "s3://test/mnist/explainer",
+								RuntimeVersion: "0.4.0",
+								Resources: v1.ResourceRequirements{
+									Requests: v1.ResourceList{
+										v1.ResourceCPU:    resource.MustParse("1"),
+										v1.ResourceMemory: resource.MustParse("2Gi"),
+									},
+									Limits: v1.ResourceList{
+										v1.ResourceCPU:    resource.MustParse("1"),
+										v1.ResourceMemory: resource.MustParse("2Gi"),
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			v1beta1Spec: &v1beta1.InferenceService{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "explainerConversionTest",
+					Namespace: "default",
+				},
+				Spec: v1beta1.InferenceServiceSpec{
+					Predictor: v1beta1.PredictorSpec{
+						ComponentExtensionSpec: v1beta1.ComponentExtensionSpec{
+							MinReplicas:          GetIntReference(1),
+							MaxReplicas:          3,
+							ContainerConcurrency: proto.Int64(1),
+						},
+						Tensorflow: &v1beta1.TFServingSpec{
+							PredictorExtensionSpec: v1beta1.PredictorExtensionSpec{
+								StorageURI:     proto.String("s3://test/mnist/export"),
+								RuntimeVersion: proto.String("1.13.0"),
+							},
+						},
+					},
+					Explainer: &v1beta1.ExplainerSpec{
+						ComponentExtensionSpec: v1beta1.ComponentExtensionSpec{
+							MinReplicas:          GetIntReference(1),
+							MaxReplicas:          3,
+							ContainerConcurrency: proto.Int64(1),
+						},
+						Alibi: &v1beta1.AlibiExplainerSpec{
+							Type:           v1beta1.AlibiAnchorsTabularExplainer,
+							StorageURI:     "s3://test/mnist/explainer",
+							RuntimeVersion: proto.String("0.4.0"),
+							Container: v1.Container{
+								Resources: v1.ResourceRequirements{
+									Requests: v1.ResourceList{
+										v1.ResourceCPU:    resource.MustParse("1"),
+										v1.ResourceMemory: resource.MustParse("2Gi"),
+									},
+									Limits: v1.ResourceList{
+										v1.ResourceCPU:    resource.MustParse("1"),
+										v1.ResourceMemory: resource.MustParse("2Gi"),
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	for name, scenario := range scenarios {
+		t.Run(name, func(t *testing.T) {
+			dst := &v1beta1.InferenceService{}
+			scenario.v1alpha2spec.ConvertTo(dst)
+			if cmp.Diff(scenario.v1beta1Spec, dst) != "" {
+				t.Errorf("diff: %s", cmp.Diff(scenario.v1beta1Spec, dst))
+			}
+			v1alpha2ExpectedSpec := &InferenceService{}
+			v1alpha2ExpectedSpec.ConvertFrom(scenario.v1beta1Spec)
+			if cmp.Diff(scenario.v1alpha2spec, v1alpha2ExpectedSpec) != "" {
+				t.Errorf("diff: %s", cmp.Diff(scenario.v1alpha2spec, v1alpha2ExpectedSpec))
+			}
+		})
+	}
+}
diff --git a/pkg/apis/serving/v1alpha2/inferenceservice_defaults_test.go b/pkg/apis/serving/v1alpha2/inferenceservice_defaults_test.go
--- a/pkg/apis/serving/v1alpha2/inferenceservice_defaults_test.go
+++ b/pkg/apis/serving/v1alpha2/inferenceservice_defaults_test.go
@@ -45,8 +45,8 @@ func TestTensorflowDefaults(t *testing.T) {
 	isvc.Spec.Canary = isvc.Spec.Default.DeepCopy()
 	isvc.Spec.Canary.Predictor.Tensorflow.RuntimeVersion = "1.11"
 	isvc.Spec.Canary.Predictor.Tensorflow.Resources.Requests = v1.ResourceList{v1.ResourceMemory: resource.MustParse("3Gi")}
-	isvc.Default(c)
-
+	isvc.applyDefaultsEndpoint(&isvc.Spec.Default, c)
+	isvc.applyDefaultsEndpoint(isvc.Spec.Canary, c)
 	g.Expect(isvc.Spec.Default.Predictor.Tensorflow.RuntimeVersion).To(gomega.Equal(DefaultTensorflowRuntimeVersion))
 	g.Expect(isvc.Spec.Default.Predictor.Tensorflow.Resources.Requests[v1.ResourceCPU]).To(gomega.Equal(defaultResource[v1.ResourceCPU]))
 	g.Expect(isvc.Spec.Default.Predictor.Tensorflow.Resources.Requests[v1.ResourceMemory]).To(gomega.Equal(defaultResource[v1.ResourceMemory]))
@@ -79,7 +79,7 @@ func TestTensorflowGPUDefaults(t *testing.T) {
 			},
 		},
 	}
-	isvc.Default(c)
+	isvc.applyDefaultsEndpoint(&isvc.Spec.Default, c)
 	g.Expect(isvc.Spec.Default.Predictor.Tensorflow.RuntimeVersion).To(gomega.Equal(DefaultTensorflowRuntimeVersionGPU))
 }
 
@@ -101,8 +101,8 @@ func TestPyTorchDefaults(t *testing.T) {
 	isvc.Spec.Canary = isvc.Spec.Default.DeepCopy()
 	isvc.Spec.Canary.Predictor.PyTorch.RuntimeVersion = constants.KFServingDefaultVersion
 	isvc.Spec.Canary.Predictor.PyTorch.Resources.Requests = v1.ResourceList{v1.ResourceMemory: resource.MustParse("3Gi")}
-	isvc.Default(c)
-
+	isvc.applyDefaultsEndpoint(isvc.Spec.Canary, c)
+	isvc.applyDefaultsEndpoint(&isvc.Spec.Default, c)
 	g.Expect(isvc.Spec.Default.Predictor.PyTorch.RuntimeVersion).To(gomega.Equal(DefaultPyTorchRuntimeVersion))
 	g.Expect(isvc.Spec.Default.Predictor.PyTorch.Resources.Requests[v1.ResourceCPU]).To(gomega.Equal(defaultResource[v1.ResourceCPU]))
 	g.Expect(isvc.Spec.Default.Predictor.PyTorch.Resources.Requests[v1.ResourceMemory]).To(gomega.Equal(defaultResource[v1.ResourceMemory]))
@@ -131,7 +131,8 @@ func TestSKLearnDefaults(t *testing.T) {
 	isvc.Spec.Canary = isvc.Spec.Default.DeepCopy()
 	isvc.Spec.Canary.Predictor.SKLearn.RuntimeVersion = constants.KFServingDefaultVersion
 	isvc.Spec.Canary.Predictor.SKLearn.Resources.Requests = v1.ResourceList{v1.ResourceMemory: resource.MustParse("3Gi")}
-	isvc.Default(c)
+	isvc.applyDefaultsEndpoint(isvc.Spec.Canary, c)
+	isvc.applyDefaultsEndpoint(&isvc.Spec.Default, c)
 
 	g.Expect(isvc.Spec.Default.Predictor.SKLearn.RuntimeVersion).To(gomega.Equal(DefaultSKLearnRuntimeVersion))
 	g.Expect(isvc.Spec.Default.Predictor.SKLearn.Resources.Requests[v1.ResourceCPU]).To(gomega.Equal(defaultResource[v1.ResourceCPU]))
@@ -161,7 +162,8 @@ func TestXGBoostDefaults(t *testing.T) {
 	isvc.Spec.Canary = isvc.Spec.Default.DeepCopy()
 	isvc.Spec.Canary.Predictor.XGBoost.RuntimeVersion = constants.KFServingDefaultVersion
 	isvc.Spec.Canary.Predictor.XGBoost.Resources.Requests = v1.ResourceList{v1.ResourceMemory: resource.MustParse("3Gi")}
-	isvc.Default(c)
+	isvc.applyDefaultsEndpoint(isvc.Spec.Canary, c)
+	isvc.applyDefaultsEndpoint(&isvc.Spec.Default, c)
 
 	g.Expect(isvc.Spec.Default.Predictor.XGBoost.RuntimeVersion).To(gomega.Equal(DefaultXGBoostRuntimeVersion))
 	g.Expect(isvc.Spec.Default.Predictor.XGBoost.Resources.Requests[v1.ResourceCPU]).To(gomega.Equal(defaultResource[v1.ResourceCPU]))
@@ -194,7 +196,8 @@ func TestONNXDefaults(t *testing.T) {
 	isvc.Spec.Canary = isvc.Spec.Default.DeepCopy()
 	isvc.Spec.Canary.Predictor.ONNX.RuntimeVersion = "0.6.0"
 	isvc.Spec.Canary.Predictor.ONNX.Resources.Requests = v1.ResourceList{v1.ResourceMemory: resource.MustParse("3Gi")}
-	isvc.Default(c)
+	isvc.applyDefaultsEndpoint(isvc.Spec.Canary, c)
+	isvc.applyDefaultsEndpoint(&isvc.Spec.Default, c)
 
 	g.Expect(isvc.Spec.Default.Predictor.ONNX.RuntimeVersion).To(gomega.Equal(DefaultONNXRuntimeVersion))
 	g.Expect(isvc.Spec.Default.Predictor.ONNX.Resources.Requests[v1.ResourceCPU]).To(gomega.Equal(defaultResource[v1.ResourceCPU]))
@@ -224,7 +227,8 @@ func TestTritonISDefaults(t *testing.T) {
 	isvc.Spec.Canary = isvc.Spec.Default.DeepCopy()
 	isvc.Spec.Canary.Predictor.Triton.RuntimeVersion = "19.09"
 	isvc.Spec.Canary.Predictor.Triton.Resources.Requests = v1.ResourceList{v1.ResourceMemory: resource.MustParse("3Gi")}
-	isvc.Default(c)
+	isvc.applyDefaultsEndpoint(isvc.Spec.Canary, c)
+	isvc.applyDefaultsEndpoint(&isvc.Spec.Default, c)
 
 	g.Expect(isvc.Spec.Default.Predictor.Triton.RuntimeVersion).To(gomega.Equal(DefaultTritonISRuntimeVersion))
 	g.Expect(isvc.Spec.Default.Predictor.Triton.Resources.Requests[v1.ResourceCPU]).To(gomega.Equal(defaultResource[v1.ResourceCPU]))
@@ -261,7 +265,8 @@ func TestAlibiExplainerDefaults(t *testing.T) {
 	isvc.Spec.Canary = isvc.Spec.Default.DeepCopy()
 	isvc.Spec.Canary.Explainer.Alibi.RuntimeVersion = "0.2.4"
 	isvc.Spec.Canary.Explainer.Alibi.Resources.Requests = v1.ResourceList{v1.ResourceMemory: resource.MustParse("3Gi")}
-	isvc.Default(c)
+	isvc.applyDefaultsEndpoint(isvc.Spec.Canary, c)
+	isvc.applyDefaultsEndpoint(&isvc.Spec.Default, c)
 
 	g.Expect(isvc.Spec.Default.Explainer.Alibi.RuntimeVersion).To(gomega.Equal(DefaultAlibiExplainerRuntimeVersion))
 	g.Expect(isvc.Spec.Default.Explainer.Alibi.Resources.Requests[v1.ResourceCPU]).To(gomega.Equal(defaultResource[v1.ResourceCPU]))
diff --git a/pkg/apis/serving/v1alpha2/inferenceservice_types_test.go b/pkg/apis/serving/v1alpha2/inferenceservice_types_test.go
--- a/pkg/apis/serving/v1alpha2/inferenceservice_types_test.go
+++ b/pkg/apis/serving/v1alpha2/inferenceservice_types_test.go
@@ -50,7 +50,7 @@ func TestInferenceService(t *testing.T) {
 					},
 				},
 			},
-			CanaryTrafficPercent: 20,
+			CanaryTrafficPercent: GetIntReference(20),
 			Canary: &EndpointSpec{
 				Predictor: PredictorSpec{
 					DeploymentSpec: DeploymentSpec{
diff --git a/pkg/apis/serving/v1alpha2/inferenceservice_validation_test.go b/pkg/apis/serving/v1alpha2/inferenceservice_validation_test.go
--- a/pkg/apis/serving/v1alpha2/inferenceservice_validation_test.go
+++ b/pkg/apis/serving/v1alpha2/inferenceservice_validation_test.go
@@ -39,7 +39,7 @@ func makeTestInferenceService() InferenceService {
 			},
 		},
 	}
-	inferenceservice.Default(c)
+	inferenceservice.applyDefaultsEndpoint(&inferenceservice.Spec.Default, c)
 	return inferenceservice
 }
 
@@ -48,7 +48,7 @@ func TestValidStorageURIPrefixOK(t *testing.T) {
 	for _, prefix := range SupportedStorageURIPrefixList {
 		isvc := makeTestInferenceService()
 		isvc.Spec.Default.Predictor.Tensorflow.StorageURI = prefix + "foo/bar"
-		g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+		g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 	}
 }
 
@@ -56,77 +56,77 @@ func TestEmptyStorageURIPrefixOK(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = ""
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+	g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 }
 
 func TestLocalPathStorageURIPrefixOK(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = "some/relative/path"
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+	g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = "/some/absolute/path"
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+	g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = "/"
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+	g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = "foo"
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+	g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 }
 
 func TestAzureBlobOK(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = "https://kfserving.blob.core.windows.net/triton/simple_string/"
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+	g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = "https://kfserving.blob.core.windows.net/triton/simple_string"
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+	g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = "https://kfserving.blob.core.windows.net/triton/"
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+	g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = "https://kfserving.blob.core.windows.net/triton"
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+	g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 }
 
 func TestAzureBlobNoAccountFails(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = "https://blob.core.windows.net/triton/simple_string/"
-	g.Expect(isvc.ValidateCreate(c)).ShouldNot(gomega.Succeed())
+	g.Expect(isvc.validate(c)).ShouldNot(gomega.Succeed())
 }
 
 func TestAzureBlobNoContainerFails(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = "https://foo.blob.core.windows.net/"
-	g.Expect(isvc.ValidateCreate(c)).ShouldNot(gomega.Succeed())
+	g.Expect(isvc.validate(c)).ShouldNot(gomega.Succeed())
 }
 
 func TestHttpStorageURIPrefixOK(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = "https://raw.githubusercontent.com/someOrg/someRepo/model.tar.gz"
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+	g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = "http://raw.githubusercontent.com/someOrg/someRepo/model.tar.gz"
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+	g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 }
 
 func TestUnkownStorageURIPrefixFails(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Default.Predictor.Tensorflow.StorageURI = "blob://foo/bar"
-	g.Expect(isvc.ValidateCreate(c)).ShouldNot(gomega.Succeed())
+	g.Expect(isvc.validate(c)).ShouldNot(gomega.Succeed())
 }
 
 func TestRejectMultipleModelSpecs(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Default.Predictor.Custom = &CustomSpec{Container: v1.Container{}}
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(ExactlyOnePredictorViolatedError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(ExactlyOnePredictorViolatedError))
 }
 
 func TestRejectModelSpecMissing(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Default.Predictor.Tensorflow = nil
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(ExactlyOnePredictorViolatedError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(ExactlyOnePredictorViolatedError))
 }
 
 func TestRejectMultipleCanaryModelSpecs(t *testing.T) {
@@ -138,7 +138,7 @@ func TestRejectMultipleCanaryModelSpecs(t *testing.T) {
 			Tensorflow: isvc.Spec.Default.Predictor.Tensorflow,
 		},
 	}
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(ExactlyOnePredictorViolatedError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(ExactlyOnePredictorViolatedError))
 }
 
 func TestRejectCanaryModelSpecMissing(t *testing.T) {
@@ -147,44 +147,44 @@ func TestRejectCanaryModelSpecMissing(t *testing.T) {
 	isvc.Spec.Canary = &EndpointSpec{
 		Predictor: PredictorSpec{},
 	}
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(ExactlyOnePredictorViolatedError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(ExactlyOnePredictorViolatedError))
 }
 
 func TestRejectBadCanaryTrafficValues(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Canary = &isvc.Spec.Default
-	isvc.Spec.CanaryTrafficPercent = -1
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(TrafficBoundsExceededError))
-	isvc.Spec.CanaryTrafficPercent = 101
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(TrafficBoundsExceededError))
+	isvc.Spec.CanaryTrafficPercent = GetIntReference(-1)
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(TrafficBoundsExceededError))
+	isvc.Spec.CanaryTrafficPercent = GetIntReference(101)
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(TrafficBoundsExceededError))
 }
 
 func TestRejectTrafficProvidedWithoutCanary(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
-	isvc.Spec.CanaryTrafficPercent = 1
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(TrafficProvidedWithoutCanaryError))
+	isvc.Spec.CanaryTrafficPercent = GetIntReference(1)
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(TrafficProvidedWithoutCanaryError))
 }
 
 func TestBadParallelismValues(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Default.Predictor.Parallelism = -1
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(ParallelismLowerBoundExceededError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(ParallelismLowerBoundExceededError))
 }
 
 func TestBadReplicaValues(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Default.Predictor.MinReplicas = GetIntReference(-1)
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(MinReplicasLowerBoundExceededError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(MinReplicasLowerBoundExceededError))
 	isvc.Spec.Default.Predictor.MinReplicas = GetIntReference(1)
 	isvc.Spec.Default.Predictor.MaxReplicas = -1
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(MaxReplicasLowerBoundExceededError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(MaxReplicasLowerBoundExceededError))
 	isvc.Spec.Default.Predictor.MinReplicas = GetIntReference(2)
 	isvc.Spec.Default.Predictor.MaxReplicas = 1
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(MinReplicasShouldBeLessThanMaxError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(MinReplicasShouldBeLessThanMaxError))
 	// Now test transformer and explainer, so set correct value for predictor
 	isvc.Spec.Default.Predictor.MinReplicas = GetIntReference(0)
 	isvc.Spec.Default.Predictor.MaxReplicas = 0
@@ -195,15 +195,15 @@ func TestBadReplicaValues(t *testing.T) {
 			Image: "custom:0.1",
 		},
 	}
-	isvc.Default(c)
+	isvc.applyDefaultsEndpoint(&isvc.Spec.Default, c)
 	isvc.Spec.Default.Transformer.MinReplicas = GetIntReference(-1)
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(MinReplicasLowerBoundExceededError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(MinReplicasLowerBoundExceededError))
 	isvc.Spec.Default.Transformer.MinReplicas = GetIntReference(1)
 	isvc.Spec.Default.Transformer.MaxReplicas = -1
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(MaxReplicasLowerBoundExceededError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(MaxReplicasLowerBoundExceededError))
 	isvc.Spec.Default.Transformer.MinReplicas = GetIntReference(2)
 	isvc.Spec.Default.Transformer.MaxReplicas = 1
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(MinReplicasShouldBeLessThanMaxError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(MinReplicasShouldBeLessThanMaxError))
 	// Now test explainer, so ignore transformer
 	isvc.Spec.Default.Transformer = nil
 
@@ -212,15 +212,15 @@ func TestBadReplicaValues(t *testing.T) {
 			StorageURI: "gs://testbucket/testmodel",
 		},
 	}
-	isvc.Default(c)
+	isvc.applyDefaultsEndpoint(&isvc.Spec.Default, c)
 	isvc.Spec.Default.Explainer.MinReplicas = GetIntReference(-1)
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(MinReplicasLowerBoundExceededError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(MinReplicasLowerBoundExceededError))
 	isvc.Spec.Default.Explainer.MinReplicas = GetIntReference(1)
 	isvc.Spec.Default.Explainer.MaxReplicas = -1
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(MaxReplicasLowerBoundExceededError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(MaxReplicasLowerBoundExceededError))
 	isvc.Spec.Default.Explainer.MinReplicas = GetIntReference(2)
 	isvc.Spec.Default.Explainer.MaxReplicas = 1
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(MinReplicasShouldBeLessThanMaxError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(MinReplicasShouldBeLessThanMaxError))
 }
 
 func TestCustomBadFields(t *testing.T) {
@@ -235,7 +235,7 @@ func TestCustomBadFields(t *testing.T) {
 			StdinOnce: true,
 		},
 	}
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError("Custom container validation error: must not set the field(s): stdin, stdinOnce"))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError("Custom container validation error: must not set the field(s): stdin, stdinOnce"))
 }
 
 func TestCustomOK(t *testing.T) {
@@ -247,23 +247,23 @@ func TestCustomOK(t *testing.T) {
 			Image: "custom:0.1",
 		},
 	}
-	err := isvc.ValidateCreate(c)
+	err := isvc.validate(c)
 	fmt.Println(err)
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+	g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 }
 
 func TestRejectBadTransformer(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Default.Transformer = &TransformerSpec{}
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(ExactlyOneTransformerViolatedError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(ExactlyOneTransformerViolatedError))
 }
 
 func TestRejectBadExplainer(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Default.Explainer = &ExplainerSpec{}
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.MatchError(ExactlyOneExplainerViolatedError))
+	g.Expect(isvc.validate(c)).Should(gomega.MatchError(ExactlyOneExplainerViolatedError))
 }
 
 func TestGoodExplainer(t *testing.T) {
@@ -274,6 +274,6 @@ func TestGoodExplainer(t *testing.T) {
 			StorageURI: "gs://testbucket/testmodel",
 		},
 	}
-	isvc.Default(c)
-	g.Expect(isvc.ValidateCreate(c)).Should(gomega.Succeed())
+	isvc.applyDefaultsEndpoint(&isvc.Spec.Default, c)
+	g.Expect(isvc.validate(c)).Should(gomega.Succeed())
 }
diff --git a/pkg/apis/serving/v1beta1/explainer_alibi_test.go b/pkg/apis/serving/v1beta1/explainer_alibi_test.go
--- a/pkg/apis/serving/v1beta1/explainer_alibi_test.go
+++ b/pkg/apis/serving/v1beta1/explainer_alibi_test.go
@@ -1,6 +1,23 @@
+/*
+Copyright 2020 kubeflow.org.
+
+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 v1beta1
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/golang/protobuf/proto"
@@ -149,7 +166,7 @@ func TestAlibiDefaulter(t *testing.T) {
 		t.Run(name, func(t *testing.T) {
 			scenario.spec.Alibi.Default(&config)
 			if !g.Expect(scenario.spec).To(gomega.Equal(scenario.expected)) {
-				t.Errorf("got %q, want %q", scenario.spec, scenario.expected)
+				t.Errorf("got %v, want %v", scenario.spec, scenario.expected)
 			}
 		})
 	}
@@ -221,7 +238,7 @@ func TestCreateAlibiModelServingContainer(t *testing.T) {
 					"--model_name",
 					"someName",
 					"--predictor_host",
-					"someName-predictor-default.default",
+					fmt.Sprintf("%s.%s", constants.DefaultPredictorServiceName("someName"), "default"),
 					"--http_port",
 					"8080",
 					"--storage_uri",
@@ -268,7 +285,7 @@ func TestCreateAlibiModelServingContainer(t *testing.T) {
 					"--model_name",
 					"someName",
 					"--predictor_host",
-					"someName-predictor-default.default",
+					fmt.Sprintf("%s.%s", constants.DefaultPredictorServiceName("someName"), "default"),
 					"--http_port",
 					"8080",
 					"--storage_uri",
@@ -321,7 +338,7 @@ func TestCreateAlibiModelServingContainer(t *testing.T) {
 					"--model_name",
 					"someName",
 					"--predictor_host",
-					"someName-predictor-default.default",
+					fmt.Sprintf("%s.%s", constants.DefaultPredictorServiceName("someName"), "default"),
 					"--http_port",
 					"8080",
 					"--workers",
diff --git a/pkg/apis/serving/v1beta1/explainer_custom_test.go b/pkg/apis/serving/v1beta1/explainer_custom_test.go
--- a/pkg/apis/serving/v1beta1/explainer_custom_test.go
+++ b/pkg/apis/serving/v1beta1/explainer_custom_test.go
@@ -1,6 +1,23 @@
+/*
+Copyright 2020 kubeflow.org.
+
+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 v1beta1
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/golang/protobuf/proto"
@@ -23,17 +40,13 @@ func TestCustomExplainerValidation(t *testing.T) {
 	}{
 		"ValidStorageUri": {
 			spec: ExplainerSpec{
-				CustomExplainer: &CustomExplainer{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "s3://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "s3://modelzoo",
 								},
 							},
 						},
@@ -44,17 +57,13 @@ func TestCustomExplainerValidation(t *testing.T) {
 		},
 		"InvalidStorageUri": {
 			spec: ExplainerSpec{
-				CustomExplainer: &CustomExplainer{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
 						},
@@ -69,17 +78,13 @@ func TestCustomExplainerValidation(t *testing.T) {
 					MinReplicas: GetIntReference(3),
 					MaxReplicas: 2,
 				},
-				CustomExplainer: &CustomExplainer{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
 						},
@@ -94,17 +99,13 @@ func TestCustomExplainerValidation(t *testing.T) {
 					MinReplicas:          GetIntReference(3),
 					ContainerConcurrency: proto.Int64(-1),
 				},
-				CustomExplainer: &CustomExplainer{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
 						},
@@ -117,8 +118,9 @@ func TestCustomExplainerValidation(t *testing.T) {
 
 	for name, scenario := range scenarios {
 		t.Run(name, func(t *testing.T) {
-			scenario.spec.CustomExplainer.Default(&config)
-			res := scenario.spec.CustomExplainer.Validate()
+			explainer := CustomExplainer{PodSpec: v1.PodSpec(scenario.spec.PodSpec)}
+			explainer.Default(&config)
+			res := explainer.Validate()
 			if !g.Expect(res).To(scenario.matcher) {
 				t.Errorf("got %q, want %q", res, scenario.matcher)
 			}
@@ -141,17 +143,13 @@ func TestCustomExplainerDefaulter(t *testing.T) {
 	}{
 		"DefaultResources": {
 			spec: ExplainerSpec{
-				CustomExplainer: &CustomExplainer{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
 						},
@@ -159,24 +157,20 @@ func TestCustomExplainerDefaulter(t *testing.T) {
 				},
 			},
 			expected: ExplainerSpec{
-				CustomExplainer: &CustomExplainer{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Name: constants.InferenceServiceContainerName,
+							Env: []v1.EnvVar{
 								{
-									Name: constants.InferenceServiceContainerName,
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
-									Resources: v1.ResourceRequirements{
-										Requests: defaultResource,
-										Limits:   defaultResource,
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
+							Resources: v1.ResourceRequirements{
+								Requests: defaultResource,
+								Limits:   defaultResource,
+							},
 						},
 					},
 				},
@@ -186,9 +180,10 @@ func TestCustomExplainerDefaulter(t *testing.T) {
 
 	for name, scenario := range scenarios {
 		t.Run(name, func(t *testing.T) {
-			scenario.spec.CustomExplainer.Default(&config)
+			explainer := CustomExplainer{PodSpec: v1.PodSpec(scenario.spec.PodSpec)}
+			explainer.Default(&config)
 			if !g.Expect(scenario.spec).To(gomega.Equal(scenario.expected)) {
-				t.Errorf("got %q, want %q", scenario.spec, scenario.expected)
+				t.Errorf("got %v, want %v", scenario.spec, scenario.expected)
 			}
 		})
 	}
@@ -236,21 +231,17 @@ func TestCreateCustomExplainerContainer(t *testing.T) {
 						},
 					},
 					Explainer: &ExplainerSpec{
-						CustomExplainer: &CustomExplainer{
-							PodTemplateSpec: v1.PodTemplateSpec{
-								Spec: v1.PodSpec{
-									Containers: []v1.Container{
+						PodSpec: PodSpec{
+							Containers: []v1.Container{
+								{
+									Image: "explainer:0.1.0",
+									Env: []v1.EnvVar{
 										{
-											Image: "explainer:0.1.0",
-											Env: []v1.EnvVar{
-												{
-													Name:  "STORAGE_URI",
-													Value: "hdfs://modelzoo",
-												},
-											},
-											Resources: requestedResource,
+											Name:  "STORAGE_URI",
+											Value: "hdfs://modelzoo",
 										},
 									},
+									Resources: requestedResource,
 								},
 							},
 						},
@@ -265,7 +256,7 @@ func TestCreateCustomExplainerContainer(t *testing.T) {
 					"--model_name",
 					"someName",
 					"--predictor_host",
-					"someName-predictor-default.default",
+					fmt.Sprintf("%s.%s", constants.DefaultPredictorServiceName("someName"), "default"),
 					"--http_port",
 					"8080",
 				},
@@ -300,21 +291,17 @@ func TestCreateCustomExplainerContainer(t *testing.T) {
 						ComponentExtensionSpec: ComponentExtensionSpec{
 							ContainerConcurrency: proto.Int64(2),
 						},
-						CustomExplainer: &CustomExplainer{
-							PodTemplateSpec: v1.PodTemplateSpec{
-								Spec: v1.PodSpec{
-									Containers: []v1.Container{
+						PodSpec: PodSpec{
+							Containers: []v1.Container{
+								{
+									Image: "explainer:0.1.0",
+									Env: []v1.EnvVar{
 										{
-											Image: "explainer:0.1.0",
-											Env: []v1.EnvVar{
-												{
-													Name:  "STORAGE_URI",
-													Value: "hdfs://modelzoo",
-												},
-											},
-											Resources: requestedResource,
+											Name:  "STORAGE_URI",
+											Value: "hdfs://modelzoo",
 										},
 									},
+									Resources: requestedResource,
 								},
 							},
 						},
@@ -329,7 +316,7 @@ func TestCreateCustomExplainerContainer(t *testing.T) {
 					"--model_name",
 					"someName",
 					"--predictor_host",
-					"someName-predictor-default.default",
+					fmt.Sprintf("%s.%s", constants.DefaultPredictorServiceName("someName"), "default"),
 					"--http_port",
 					"8080",
 					"--workers",
diff --git a/pkg/apis/serving/v1beta1/inference_service_defaults_test.go b/pkg/apis/serving/v1beta1/inference_service_defaults_test.go
--- a/pkg/apis/serving/v1beta1/inference_service_defaults_test.go
+++ b/pkg/apis/serving/v1beta1/inference_service_defaults_test.go
@@ -56,17 +56,13 @@ func TestInferenceServiceDefaults(t *testing.T) {
 				},
 			},
 			Transformer: &TransformerSpec{
-				CustomTransformer: &CustomTransformer{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "s3://transformer",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "s3://transformer",
 								},
 							},
 						},
@@ -82,12 +78,57 @@ func TestInferenceServiceDefaults(t *testing.T) {
 	}
 	resources := v1.ResourceRequirements{Requests: defaultResource, Limits: defaultResource}
 	isvc.Spec.DeepCopy()
-	isvc.defaultInferenceService(config)
+	isvc.DefaultInferenceService(config)
 	g.Expect(*isvc.Spec.Predictor.Tensorflow.RuntimeVersion).To(gomega.Equal("1.14.0"))
 	g.Expect(isvc.Spec.Predictor.Tensorflow.Resources).To(gomega.Equal(resources))
 
-	g.Expect(isvc.Spec.Transformer.CustomTransformer.Spec.Containers[0].Resources).To(gomega.Equal(resources))
+	g.Expect(isvc.Spec.Transformer.PodSpec.Containers[0].Resources).To(gomega.Equal(resources))
 
 	g.Expect(*isvc.Spec.Explainer.Alibi.RuntimeVersion).To(gomega.Equal("v0.4.0"))
 	g.Expect(isvc.Spec.Explainer.Alibi.Resources).To(gomega.Equal(resources))
 }
+
+func TestCustomPredictorDefaults(t *testing.T) {
+	g := gomega.NewGomegaWithT(t)
+	config := &InferenceServicesConfig{
+		Predictors: PredictorsConfig{
+			Tensorflow: PredictorConfig{
+				ContainerImage:         "tfserving",
+				DefaultImageVersion:    "1.14.0",
+				DefaultGpuImageVersion: "1.14.0-gpu",
+			},
+		},
+		Explainers: ExplainersConfig{
+			AlibiExplainer: ExplainerConfig{
+				ContainerImage:      "alibi",
+				DefaultImageVersion: "v0.4.0",
+			},
+		},
+	}
+	isvc := InferenceService{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "foo",
+			Namespace: "default",
+		},
+		Spec: InferenceServiceSpec{
+			Predictor: PredictorSpec{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
+								{
+									Name:  "STORAGE_URI",
+									Value: "s3://transformer",
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	resources := v1.ResourceRequirements{Requests: defaultResource, Limits: defaultResource}
+	isvc.Spec.DeepCopy()
+	isvc.DefaultInferenceService(config)
+	g.Expect(isvc.Spec.Predictor.PodSpec.Containers[0].Resources).To(gomega.Equal(resources))
+}
diff --git a/pkg/apis/serving/v1beta1/inference_service_status_test.go b/pkg/apis/serving/v1beta1/inference_service_status_test.go
--- a/pkg/apis/serving/v1beta1/inference_service_status_test.go
+++ b/pkg/apis/serving/v1beta1/inference_service_status_test.go
@@ -101,24 +101,44 @@ func TestInferenceServiceIsReady(t *testing.T) {
 		name: "True condition status should be ready",
 		ServiceStatus: knservingv1.ServiceStatus{
 			Status: duckv1.Status{
-				Conditions: duckv1.Conditions{{
-					Type:   knservingv1.ConfigurationConditionReady,
-					Status: v1.ConditionTrue,
-				}},
+				Conditions: duckv1.Conditions{
+					{
+						Type:   "ConfigurationsReady",
+						Status: v1.ConditionTrue,
+					},
+					{
+						Type:   "RoutesReady",
+						Status: v1.ConditionTrue,
+					},
+					{
+						Type:   knservingv1.ServiceConditionReady,
+						Status: v1.ConditionTrue,
+					},
+				},
 			},
 		},
 		isReady: true,
 	}, {
-		name: "Default service, route conditions with ready status should be ready",
+		name: "Conditions with ready status should be ready",
 		ServiceStatus: knservingv1.ServiceStatus{
 			Status: duckv1.Status{
-				Conditions: duckv1.Conditions{{
-					Type:   "Foo",
-					Status: v1.ConditionTrue,
-				}, {
-					Type:   knservingv1.ConfigurationConditionReady,
-					Status: v1.ConditionTrue,
-				},
+				Conditions: duckv1.Conditions{
+					{
+						Type:   "Foo",
+						Status: v1.ConditionTrue,
+					},
+					{
+						Type:   "RoutesReady",
+						Status: v1.ConditionTrue,
+					},
+					{
+						Type:   "ConfigurationsReady",
+						Status: v1.ConditionTrue,
+					},
+					{
+						Type:   knservingv1.ServiceConditionReady,
+						Status: v1.ConditionTrue,
+					},
 				},
 			},
 		},
@@ -127,13 +147,16 @@ func TestInferenceServiceIsReady(t *testing.T) {
 		name: "Multiple conditions with ready status false should not be ready",
 		ServiceStatus: knservingv1.ServiceStatus{
 			Status: duckv1.Status{
-				Conditions: duckv1.Conditions{{
-					Type:   "Foo",
-					Status: v1.ConditionTrue,
-				}, {
-					Type:   knservingv1.ConfigurationConditionReady,
-					Status: v1.ConditionFalse,
-				}},
+				Conditions: duckv1.Conditions{
+					{
+						Type:   "Foo",
+						Status: v1.ConditionTrue,
+					},
+					{
+						Type:   knservingv1.ConfigurationConditionReady,
+						Status: v1.ConditionFalse,
+					},
+				},
 			},
 		},
 		isReady: false,
@@ -143,7 +166,7 @@ func TestInferenceServiceIsReady(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			status := InferenceServiceStatus{}
 			status.PropagateStatus(PredictorComponent, &tc.ServiceStatus)
-			if e, a := tc.isReady, status.IsReady(); e != a {
+			if e, a := tc.isReady, status.IsConditionReady(PredictorReady); e != a {
 				t.Errorf("%q expected: %v got: %v conditions: %v", tc.name, e, a, status.Conditions)
 			}
 		})
diff --git a/pkg/apis/serving/v1beta1/inference_service_validation_test.go b/pkg/apis/serving/v1beta1/inference_service_validation_test.go
--- a/pkg/apis/serving/v1beta1/inference_service_validation_test.go
+++ b/pkg/apis/serving/v1beta1/inference_service_validation_test.go
@@ -103,6 +103,15 @@ func TestAzureBlobNoContainerFails(t *testing.T) {
 	g.Expect(isvc.ValidateCreate()).ShouldNot(gomega.Succeed())
 }
 
+func TestHttpStorageURIPrefixOK(t *testing.T) {
+	g := gomega.NewGomegaWithT(t)
+	isvc := makeTestInferenceService()
+	isvc.Spec.Predictor.Tensorflow.StorageURI = proto.String("https://raw.githubusercontent.com/someOrg/someRepo/model.tar.gz")
+	g.Expect(isvc.ValidateCreate()).Should(gomega.Succeed())
+	isvc.Spec.Predictor.Tensorflow.StorageURI = proto.String("http://raw.githubusercontent.com/someOrg/someRepo/model.tar.gz")
+	g.Expect(isvc.ValidateCreate()).Should(gomega.Succeed())
+}
+
 func TestUnkownStorageURIPrefixFails(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
@@ -120,11 +129,7 @@ func TestRejectMultipleModelSpecs(t *testing.T) {
 func TestModelSpecAndCustomOverridesIsValid(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
-	isvc.Spec.Predictor.CustomPredictor = &CustomPredictor{
-		PodTemplateSpec: v1.PodTemplateSpec{
-			Spec: v1.PodSpec{},
-		},
-	}
+	isvc.Spec.Predictor.PodSpec = PodSpec{ServiceAccountName: "test"}
 	g.Expect(isvc.ValidateCreate()).Should(gomega.Succeed())
 }
 
@@ -158,14 +163,10 @@ func TestBadReplicaValues(t *testing.T) {
 	isvc.Spec.Predictor.MaxReplicas = 0
 
 	isvc.Spec.Transformer = &TransformerSpec{}
-	isvc.Spec.Transformer.CustomTransformer = &CustomTransformer{
-		PodTemplateSpec: v1.PodTemplateSpec{
-			Spec: v1.PodSpec{
-				Containers: []v1.Container{
-					{
-						Image: "some-image",
-					},
-				},
+	isvc.Spec.Transformer.PodSpec = PodSpec{
+		Containers: []v1.Container{
+			{
+				Image: "some-image",
 			},
 		},
 	}
@@ -199,14 +200,10 @@ func TestCustomOK(t *testing.T) {
 	g := gomega.NewGomegaWithT(t)
 	isvc := makeTestInferenceService()
 	isvc.Spec.Predictor.Tensorflow = nil
-	isvc.Spec.Predictor.CustomPredictor = &CustomPredictor{
-		PodTemplateSpec: v1.PodTemplateSpec{
-			Spec: v1.PodSpec{
-				Containers: []v1.Container{
-					{
-						Image: "some-image",
-					},
-				},
+	isvc.Spec.Predictor.PodSpec = PodSpec{
+		Containers: []v1.Container{
+			{
+				Image: "some-image",
 			},
 		},
 	}
diff --git a/pkg/apis/serving/v1beta1/predictor_custom_test.go b/pkg/apis/serving/v1beta1/predictor_custom_test.go
--- a/pkg/apis/serving/v1beta1/predictor_custom_test.go
+++ b/pkg/apis/serving/v1beta1/predictor_custom_test.go
@@ -1,3 +1,19 @@
+/*
+Copyright 2020 kubeflow.org.
+
+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 v1beta1
 
 import (
@@ -21,17 +37,13 @@ func TestCustomPredictorValidation(t *testing.T) {
 	}{
 		"ValidStorageUri": {
 			spec: PredictorSpec{
-				CustomPredictor: &CustomPredictor{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "s3://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "s3://modelzoo",
 								},
 							},
 						},
@@ -42,17 +54,13 @@ func TestCustomPredictorValidation(t *testing.T) {
 		},
 		"InvalidStorageUri": {
 			spec: PredictorSpec{
-				CustomPredictor: &CustomPredictor{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
 						},
@@ -67,17 +75,13 @@ func TestCustomPredictorValidation(t *testing.T) {
 					MinReplicas: GetIntReference(3),
 					MaxReplicas: 2,
 				},
-				CustomPredictor: &CustomPredictor{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
 						},
@@ -92,17 +96,13 @@ func TestCustomPredictorValidation(t *testing.T) {
 					MinReplicas:          GetIntReference(3),
 					ContainerConcurrency: proto.Int64(-1),
 				},
-				CustomPredictor: &CustomPredictor{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
 						},
@@ -115,7 +115,8 @@ func TestCustomPredictorValidation(t *testing.T) {
 
 	for name, scenario := range scenarios {
 		t.Run(name, func(t *testing.T) {
-			res := scenario.spec.CustomPredictor.Validate()
+			customPredictor := NewCustomPredictor(&scenario.spec.PodSpec)
+			res := customPredictor.Validate()
 			if !g.Expect(res).To(scenario.matcher) {
 				t.Errorf("got %q, want %q", res, scenario.matcher)
 			}
@@ -138,17 +139,13 @@ func TestCustomPredictorDefaulter(t *testing.T) {
 	}{
 		"DefaultResources": {
 			spec: PredictorSpec{
-				CustomPredictor: &CustomPredictor{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
 						},
@@ -156,24 +153,20 @@ func TestCustomPredictorDefaulter(t *testing.T) {
 				},
 			},
 			expected: PredictorSpec{
-				CustomPredictor: &CustomPredictor{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Name: constants.InferenceServiceContainerName,
+							Env: []v1.EnvVar{
 								{
-									Name: constants.InferenceServiceContainerName,
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
-									Resources: v1.ResourceRequirements{
-										Requests: defaultResource,
-										Limits:   defaultResource,
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
+							Resources: v1.ResourceRequirements{
+								Requests: defaultResource,
+								Limits:   defaultResource,
+							},
 						},
 					},
 				},
@@ -183,9 +176,10 @@ func TestCustomPredictorDefaulter(t *testing.T) {
 
 	for name, scenario := range scenarios {
 		t.Run(name, func(t *testing.T) {
-			scenario.spec.CustomPredictor.Default(&config)
+			customPredictor := NewCustomPredictor(&scenario.spec.PodSpec)
+			customPredictor.Default(&config)
 			if !g.Expect(scenario.spec).To(gomega.Equal(scenario.expected)) {
-				t.Errorf("got %q, want %q", scenario.spec, scenario.expected)
+				t.Errorf("got %v, want %v", scenario.spec, scenario.expected)
 			}
 		})
 	}
@@ -222,82 +216,23 @@ func TestCreateCustomPredictorContainer(t *testing.T) {
 				},
 				Spec: InferenceServiceSpec{
 					Predictor: PredictorSpec{
-						CustomPredictor: &CustomPredictor{
-							PodTemplateSpec: v1.PodTemplateSpec{
-								Spec: v1.PodSpec{
-									Containers: []v1.Container{
-										{
-											Image: "custom-predictor:0.1.0",
-											Args: []string{
-												"--model_name",
-												"someName",
-												"--http_port",
-												"8080",
-											},
-											Env: []v1.EnvVar{
-												{
-													Name:  "STORAGE_URI",
-													Value: "hdfs://modelzoo",
-												},
-											},
-											Resources: requestedResource,
-										},
+						PodSpec: PodSpec{
+							Containers: []v1.Container{
+								{
+									Image: "custom-predictor:0.1.0",
+									Args: []string{
+										"--model_name",
+										"someName",
+										"--http_port",
+										"8080",
 									},
-								},
-							},
-						},
-					},
-				},
-			},
-			expectedContainerSpec: &v1.Container{
-				Image:     "custom-predictor:0.1.0",
-				Name:      constants.InferenceServiceContainerName,
-				Resources: requestedResource,
-				Args: []string{
-					"--model_name",
-					"someName",
-					"--http_port",
-					"8080",
-				},
-				Env: []v1.EnvVar{
-					{
-						Name:  "STORAGE_URI",
-						Value: "hdfs://modelzoo",
-					},
-				},
-			},
-		},
-		"ContainerSpecWithContainerConcurrency": {
-			isvc: InferenceService{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "custom-predictor",
-				},
-				Spec: InferenceServiceSpec{
-					Predictor: PredictorSpec{
-						ComponentExtensionSpec: ComponentExtensionSpec{
-							ContainerConcurrency: proto.Int64(2),
-						},
-						CustomPredictor: &CustomPredictor{
-							PodTemplateSpec: v1.PodTemplateSpec{
-								Spec: v1.PodSpec{
-									Containers: []v1.Container{
+									Env: []v1.EnvVar{
 										{
-											Image: "custom-predictor:0.1.0",
-											Args: []string{
-												"--model_name",
-												"someName",
-												"--http_port",
-												"8080",
-											},
-											Env: []v1.EnvVar{
-												{
-													Name:  "STORAGE_URI",
-													Value: "hdfs://modelzoo",
-												},
-											},
-											Resources: requestedResource,
+											Name:  "STORAGE_URI",
+											Value: "hdfs://modelzoo",
 										},
 									},
+									Resources: requestedResource,
 								},
 							},
 						},
@@ -313,8 +248,6 @@ func TestCreateCustomPredictorContainer(t *testing.T) {
 					"someName",
 					"--http_port",
 					"8080",
-					"--workers",
-					"2",
 				},
 				Env: []v1.EnvVar{
 					{
diff --git a/pkg/apis/serving/v1beta1/predictor_onnxruntime_test.go b/pkg/apis/serving/v1beta1/predictor_onnxruntime_test.go
--- a/pkg/apis/serving/v1beta1/predictor_onnxruntime_test.go
+++ b/pkg/apis/serving/v1beta1/predictor_onnxruntime_test.go
@@ -174,7 +174,7 @@ func TestONNXRuntimeDefaulter(t *testing.T) {
 		t.Run(name, func(t *testing.T) {
 			scenario.spec.ONNX.Default(&config)
 			if !g.Expect(scenario.spec).To(gomega.Equal(scenario.expected)) {
-				t.Errorf("got %q, want %q", scenario.spec, scenario.expected)
+				t.Errorf("got %v, want %v", scenario.spec, scenario.expected)
 			}
 		})
 	}
diff --git a/pkg/apis/serving/v1beta1/predictor_sklearn_test.go b/pkg/apis/serving/v1beta1/predictor_sklearn_test.go
--- a/pkg/apis/serving/v1beta1/predictor_sklearn_test.go
+++ b/pkg/apis/serving/v1beta1/predictor_sklearn_test.go
@@ -174,7 +174,7 @@ func TestSKLearnDefaulter(t *testing.T) {
 		t.Run(name, func(t *testing.T) {
 			scenario.spec.SKLearn.Default(&config)
 			if !g.Expect(scenario.spec).To(gomega.Equal(scenario.expected)) {
-				t.Errorf("got %q, want %q", scenario.spec, scenario.expected)
+				t.Errorf("got %v, want %v", scenario.spec, scenario.expected)
 			}
 		})
 	}
diff --git a/pkg/apis/serving/v1beta1/predictor_tfserving_test.go b/pkg/apis/serving/v1beta1/predictor_tfserving_test.go
--- a/pkg/apis/serving/v1beta1/predictor_tfserving_test.go
+++ b/pkg/apis/serving/v1beta1/predictor_tfserving_test.go
@@ -241,7 +241,7 @@ func TestTensorflowDefaulter(t *testing.T) {
 		t.Run(name, func(t *testing.T) {
 			scenario.spec.Tensorflow.Default(&config)
 			if !g.Expect(scenario.spec).To(gomega.Equal(scenario.expected)) {
-				t.Errorf("got %q, want %q", scenario.spec, scenario.expected)
+				t.Errorf("got %v, want %v", scenario.spec, scenario.expected)
 			}
 		})
 	}
diff --git a/pkg/apis/serving/v1beta1/predictor_triton_test.go b/pkg/apis/serving/v1beta1/predictor_triton_test.go
--- a/pkg/apis/serving/v1beta1/predictor_triton_test.go
+++ b/pkg/apis/serving/v1beta1/predictor_triton_test.go
@@ -174,7 +174,7 @@ func TestTritonDefaulter(t *testing.T) {
 		t.Run(name, func(t *testing.T) {
 			scenario.spec.Triton.Default(&config)
 			if !g.Expect(scenario.spec).To(gomega.Equal(scenario.expected)) {
-				t.Errorf("got %q, want %q", scenario.spec, scenario.expected)
+				t.Errorf("got %v, want %v", scenario.spec, scenario.expected)
 			}
 		})
 	}
@@ -230,14 +230,11 @@ func TestCreateTritonContainer(t *testing.T) {
 				Image:     "tritonserver:20.03-py3",
 				Name:      constants.InferenceServiceContainerName,
 				Resources: requestedResource,
-				Command: []string{
-					"trtserver",
-				},
 				Args: []string{
+					"tritonserver",
 					"--model-store=/mnt/models",
 					"--grpc-port=9000",
 					"--http-port=8080",
-					"--allow-poll-model-repository=false",
 					"--allow-grpc=true",
 					"--allow-http=true",
 				},
@@ -266,14 +263,11 @@ func TestCreateTritonContainer(t *testing.T) {
 				Image:     "tritonserver:19.10-py3",
 				Name:      constants.InferenceServiceContainerName,
 				Resources: requestedResource,
-				Command: []string{
-					"trtserver",
-				},
 				Args: []string{
+					"tritonserver",
 					"--model-store=/mnt/models",
 					"--grpc-port=9000",
 					"--http-port=8080",
-					"--allow-poll-model-repository=false",
 					"--allow-grpc=true",
 					"--allow-http=true",
 				},
@@ -305,14 +299,11 @@ func TestCreateTritonContainer(t *testing.T) {
 				Image:     "tritonserver:20.03-py3",
 				Name:      constants.InferenceServiceContainerName,
 				Resources: requestedResource,
-				Command: []string{
-					"trtserver",
-				},
 				Args: []string{
+					"tritonserver",
 					"--model-store=/mnt/models",
 					"--grpc-port=9000",
 					"--http-port=8080",
-					"--allow-poll-model-repository=false",
 					"--allow-grpc=true",
 					"--allow-http=true",
 					"--http-thread-count=4",
diff --git a/pkg/apis/serving/v1beta1/predictor_xgboost_test.go b/pkg/apis/serving/v1beta1/predictor_xgboost_test.go
--- a/pkg/apis/serving/v1beta1/predictor_xgboost_test.go
+++ b/pkg/apis/serving/v1beta1/predictor_xgboost_test.go
@@ -174,7 +174,7 @@ func TestXGBoostDefaulter(t *testing.T) {
 		t.Run(name, func(t *testing.T) {
 			scenario.spec.XGBoost.Default(&config)
 			if !g.Expect(scenario.spec).To(gomega.Equal(scenario.expected)) {
-				t.Errorf("got %q, want %q", scenario.spec, scenario.expected)
+				t.Errorf("got %v, want %v", scenario.spec, scenario.expected)
 			}
 		})
 	}
@@ -184,19 +184,15 @@ func TestCreateXGBoostModelServingContainer(t *testing.T) {
 
 	var requestedResource = v1.ResourceRequirements{
 		Limits: v1.ResourceList{
-			"cpu": resource.Quantity{
-				Format: "100",
-			},
+			"cpu": resource.MustParse("100m"),
 		},
 		Requests: v1.ResourceList{
-			"cpu": resource.Quantity{
-				Format: "90",
-			},
+			"cpu": resource.MustParse("90m"),
 		},
 	}
 	var config = InferenceServicesConfig{
 		Predictors: PredictorsConfig{
-			SKlearn: PredictorConfig{
+			XGBoost: PredictorConfig{
 				ContainerImage:      "someOtherImage",
 				DefaultImageVersion: "0.1.0",
 			},
@@ -234,13 +230,14 @@ func TestCreateXGBoostModelServingContainer(t *testing.T) {
 					"--model_name=someName",
 					"--model_dir=/mnt/models",
 					"--http_port=8080",
+					"--nthread=1",
 				},
 			},
 		},
 		"ContainerSpecWithCustomImage": {
 			isvc: InferenceService{
 				ObjectMeta: metav1.ObjectMeta{
-					Name: "sklearn",
+					Name: "xgboost",
 				},
 				Spec: InferenceServiceSpec{
 					Predictor: PredictorSpec{
@@ -264,6 +261,7 @@ func TestCreateXGBoostModelServingContainer(t *testing.T) {
 					"--model_name=someName",
 					"--model_dir=/mnt/models",
 					"--http_port=8080",
+					"--nthread=1",
 				},
 			},
 		},
@@ -297,6 +295,7 @@ func TestCreateXGBoostModelServingContainer(t *testing.T) {
 					"--model_name=someName",
 					"--model_dir=/mnt/models",
 					"--http_port=8080",
+					"--nthread=1",
 					"--workers=1",
 				},
 			},
diff --git a/pkg/apis/serving/v1beta1/transformer_custom_test.go b/pkg/apis/serving/v1beta1/transformer_custom_test.go
--- a/pkg/apis/serving/v1beta1/transformer_custom_test.go
+++ b/pkg/apis/serving/v1beta1/transformer_custom_test.go
@@ -1,6 +1,23 @@
+/*
+Copyright 2020 kubeflow.org.
+
+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 v1beta1
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/golang/protobuf/proto"
@@ -21,17 +38,13 @@ func TestTransformerValidation(t *testing.T) {
 	}{
 		"ValidStorageUri": {
 			spec: TransformerSpec{
-				CustomTransformer: &CustomTransformer{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "s3://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "s3://modelzoo",
 								},
 							},
 						},
@@ -42,17 +55,13 @@ func TestTransformerValidation(t *testing.T) {
 		},
 		"InvalidStorageUri": {
 			spec: TransformerSpec{
-				CustomTransformer: &CustomTransformer{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
 						},
@@ -67,17 +76,13 @@ func TestTransformerValidation(t *testing.T) {
 					MinReplicas: GetIntReference(3),
 					MaxReplicas: 2,
 				},
-				CustomTransformer: &CustomTransformer{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
 						},
@@ -92,17 +97,13 @@ func TestTransformerValidation(t *testing.T) {
 					MinReplicas:          GetIntReference(3),
 					ContainerConcurrency: proto.Int64(-1),
 				},
-				CustomTransformer: &CustomTransformer{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
 						},
@@ -115,7 +116,8 @@ func TestTransformerValidation(t *testing.T) {
 
 	for name, scenario := range scenarios {
 		t.Run(name, func(t *testing.T) {
-			res := scenario.spec.CustomTransformer.Validate()
+			CustomTransformer := NewCustomTransformer(&scenario.spec.PodSpec)
+			res := CustomTransformer.Validate()
 			if !g.Expect(res).To(scenario.matcher) {
 				t.Errorf("got %q, want %q", res, scenario.matcher)
 			}
@@ -138,17 +140,13 @@ func TestTransformerDefaulter(t *testing.T) {
 	}{
 		"DefaultResources": {
 			spec: TransformerSpec{
-				CustomTransformer: &CustomTransformer{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Env: []v1.EnvVar{
 								{
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
 						},
@@ -156,24 +154,20 @@ func TestTransformerDefaulter(t *testing.T) {
 				},
 			},
 			expected: TransformerSpec{
-				CustomTransformer: &CustomTransformer{
-					PodTemplateSpec: v1.PodTemplateSpec{
-						Spec: v1.PodSpec{
-							Containers: []v1.Container{
+				PodSpec: PodSpec{
+					Containers: []v1.Container{
+						{
+							Name: constants.InferenceServiceContainerName,
+							Env: []v1.EnvVar{
 								{
-									Name: constants.InferenceServiceContainerName,
-									Env: []v1.EnvVar{
-										{
-											Name:  "STORAGE_URI",
-											Value: "hdfs://modelzoo",
-										},
-									},
-									Resources: v1.ResourceRequirements{
-										Requests: defaultResource,
-										Limits:   defaultResource,
-									},
+									Name:  "STORAGE_URI",
+									Value: "hdfs://modelzoo",
 								},
 							},
+							Resources: v1.ResourceRequirements{
+								Requests: defaultResource,
+								Limits:   defaultResource,
+							},
 						},
 					},
 				},
@@ -183,9 +177,10 @@ func TestTransformerDefaulter(t *testing.T) {
 
 	for name, scenario := range scenarios {
 		t.Run(name, func(t *testing.T) {
-			scenario.spec.CustomTransformer.Default(&config)
+			CustomTransformer := NewCustomTransformer(&scenario.spec.PodSpec)
+			CustomTransformer.Default(&config)
 			if !g.Expect(scenario.spec).To(gomega.Equal(scenario.expected)) {
-				t.Errorf("got %q, want %q", scenario.spec, scenario.expected)
+				t.Errorf("got %v, want %v", scenario.spec, scenario.expected)
 			}
 		})
 	}
@@ -233,21 +228,17 @@ func TestCreateTransformerContainer(t *testing.T) {
 						},
 					},
 					Transformer: &TransformerSpec{
-						CustomTransformer: &CustomTransformer{
-							PodTemplateSpec: v1.PodTemplateSpec{
-								Spec: v1.PodSpec{
-									Containers: []v1.Container{
+						PodSpec: PodSpec{
+							Containers: []v1.Container{
+								{
+									Image: "transformer:0.1.0",
+									Env: []v1.EnvVar{
 										{
-											Image: "transformer:0.1.0",
-											Env: []v1.EnvVar{
-												{
-													Name:  "STORAGE_URI",
-													Value: "hdfs://modelzoo",
-												},
-											},
-											Resources: requestedResource,
+											Name:  "STORAGE_URI",
+											Value: "hdfs://modelzoo",
 										},
 									},
+									Resources: requestedResource,
 								},
 							},
 						},
@@ -262,7 +253,7 @@ func TestCreateTransformerContainer(t *testing.T) {
 					"--model_name",
 					"someName",
 					"--predictor_host",
-					"someName-predictor-default.default",
+					fmt.Sprintf("%s.%s", constants.DefaultPredictorServiceName("someName"), "default"),
 					"--http_port",
 					"8080",
 				},
@@ -297,21 +288,17 @@ func TestCreateTransformerContainer(t *testing.T) {
 						ComponentExtensionSpec: ComponentExtensionSpec{
 							ContainerConcurrency: proto.Int64(2),
 						},
-						CustomTransformer: &CustomTransformer{
-							PodTemplateSpec: v1.PodTemplateSpec{
-								Spec: v1.PodSpec{
-									Containers: []v1.Container{
+						PodSpec: PodSpec{
+							Containers: []v1.Container{
+								{
+									Image: "transformer:0.1.0",
+									Env: []v1.EnvVar{
 										{
-											Image: "transformer:0.1.0",
-											Env: []v1.EnvVar{
-												{
-													Name:  "STORAGE_URI",
-													Value: "hdfs://modelzoo",
-												},
-											},
-											Resources: requestedResource,
+											Name:  "STORAGE_URI",
+											Value: "hdfs://modelzoo",
 										},
 									},
+									Resources: requestedResource,
 								},
 							},
 						},
@@ -326,7 +313,7 @@ func TestCreateTransformerContainer(t *testing.T) {
 					"--model_name",
 					"someName",
 					"--predictor_host",
-					"someName-predictor-default.default",
+					fmt.Sprintf("%s.%s", constants.DefaultPredictorServiceName("someName"), "default"),
 					"--http_port",
 					"8080",
 					"--workers",
diff --git a/pkg/controller/inferenceservice/controller_test.go b/pkg/controller/v1alpha2/inferenceservice/controller_test.go
similarity index 99%
rename from pkg/controller/inferenceservice/controller_test.go
rename to pkg/controller/v1alpha2/inferenceservice/controller_test.go
--- a/pkg/controller/inferenceservice/controller_test.go
+++ b/pkg/controller/v1alpha2/inferenceservice/controller_test.go
@@ -394,7 +394,7 @@ var _ = Describe("test inference service controller", func() {
 							},
 						},
 					},
-					CanaryTrafficPercent: 20,
+					CanaryTrafficPercent: kfserving.GetIntReference(20),
 					Canary: &kfserving.EndpointSpec{
 						Predictor: kfserving.PredictorSpec{
 							DeploymentSpec: kfserving.DeploymentSpec{
@@ -704,7 +704,7 @@ var _ = Describe("test inference service controller", func() {
 							},
 						},
 					},
-					CanaryTrafficPercent: 20,
+					CanaryTrafficPercent: kfserving.GetIntReference(20),
 					Canary: &kfserving.EndpointSpec{
 						Predictor: kfserving.PredictorSpec{
 							DeploymentSpec: kfserving.DeploymentSpec{
@@ -846,10 +846,12 @@ var _ = Describe("test inference service controller", func() {
 			// Update instance to remove Canary Spec
 			// Canary service should be removed during reconcile
 			canaryUpdate.Spec.Canary = nil
-			canaryUpdate.Spec.CanaryTrafficPercent = 0
+			canaryUpdate.Spec.CanaryTrafficPercent = kfserving.GetIntReference(0)
 			err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
 				return k8sClient.Update(context.TODO(), canaryUpdate)
 			})
+			g.Expect(err).NotTo(gomega.HaveOccurred())
+
 			// Need to wait for update propagate back to controller before checking
 			canaryDelete := &kfserving.InferenceService{}
 			g.Eventually(func() bool {
@@ -858,11 +860,6 @@ var _ = Describe("test inference service controller", func() {
 				}
 				return canaryDelete.Spec.Canary == nil
 			}, timeout).Should(gomega.BeTrue())
-			// Trigger another reconcile
-			err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
-				return k8sClient.Update(context.TODO(), canaryDelete)
-			})
-			g.Expect(err).NotTo(gomega.HaveOccurred())
 
 			defaultService = &knservingv1.Service{}
 			g.Eventually(func() error { return k8sClient.Get(context.TODO(), defaultPredictor, defaultService) }, timeout).
@@ -972,7 +969,7 @@ var _ = Describe("test inference service controller", func() {
 							},
 						},
 					},
-					CanaryTrafficPercent: 20,
+					CanaryTrafficPercent: kfserving.GetIntReference(20),
 					Canary: &kfserving.EndpointSpec{
 						Predictor: kfserving.PredictorSpec{
 							DeploymentSpec: kfserving.DeploymentSpec{
@@ -1337,7 +1334,7 @@ var _ = Describe("test inference service controller", func() {
 							},
 						},
 					},
-					CanaryTrafficPercent: 20,
+					CanaryTrafficPercent: kfserving.GetIntReference(20),
 					Canary: &kfserving.EndpointSpec{
 						Predictor: kfserving.PredictorSpec{
 							DeploymentSpec: kfserving.DeploymentSpec{
@@ -1504,7 +1501,7 @@ var _ = Describe("test inference service controller", func() {
 							},
 						},
 					},
-					CanaryTrafficPercent: 20,
+					CanaryTrafficPercent: kfserving.GetIntReference(20),
 					Canary: &kfserving.EndpointSpec{
 						Predictor: kfserving.PredictorSpec{
 							DeploymentSpec: kfserving.DeploymentSpec{
diff --git a/pkg/controller/inferenceservice/reconcilers/knative/service_reconciler_test.go b/pkg/controller/v1alpha2/inferenceservice/reconcilers/knative/service_reconciler_test.go
similarity index 100%
rename from pkg/controller/inferenceservice/reconcilers/knative/service_reconciler_test.go
rename to pkg/controller/v1alpha2/inferenceservice/reconcilers/knative/service_reconciler_test.go
diff --git a/pkg/controller/inferenceservice/reconcilers/knative/suite_test.go b/pkg/controller/v1alpha2/inferenceservice/reconcilers/knative/suite_test.go
similarity index 100%
rename from pkg/controller/inferenceservice/reconcilers/knative/suite_test.go
rename to pkg/controller/v1alpha2/inferenceservice/reconcilers/knative/suite_test.go
diff --git a/pkg/controller/inferenceservice/resources/istio/virtualservice_test.go b/pkg/controller/v1alpha2/inferenceservice/resources/istio/virtualservice_test.go
similarity index 99%
rename from pkg/controller/inferenceservice/resources/istio/virtualservice_test.go
rename to pkg/controller/v1alpha2/inferenceservice/resources/istio/virtualservice_test.go
--- a/pkg/controller/inferenceservice/resources/istio/virtualservice_test.go
+++ b/pkg/controller/v1alpha2/inferenceservice/resources/istio/virtualservice_test.go
@@ -544,7 +544,7 @@ func TestCreateVirtualService(t *testing.T) {
 					Transformer: createMockTransformerSpec(tc.canaryStatus),
 				}
 				testIsvc.Spec.Canary = canarySpec
-				testIsvc.Spec.CanaryTrafficPercent = 20
+				testIsvc.Spec.CanaryTrafficPercent = v1alpha2.GetIntReference(20)
 			}
 
 			serviceBuilder := VirtualServiceBuilder{
diff --git a/pkg/controller/inferenceservice/resources/knative/service_test.go b/pkg/controller/v1alpha2/inferenceservice/resources/knative/service_test.go
similarity index 99%
rename from pkg/controller/inferenceservice/resources/knative/service_test.go
rename to pkg/controller/v1alpha2/inferenceservice/resources/knative/service_test.go
--- a/pkg/controller/inferenceservice/resources/knative/service_test.go
+++ b/pkg/controller/v1alpha2/inferenceservice/resources/knative/service_test.go
@@ -233,7 +233,7 @@ func TestInferenceServiceToKnativeService(t *testing.T) {
 							},
 						},
 					},
-					CanaryTrafficPercent: 20,
+					CanaryTrafficPercent: v1alpha2.GetIntReference(20),
 					Canary: &v1alpha2.EndpointSpec{
 						Predictor: v1alpha2.PredictorSpec{
 							DeploymentSpec: v1alpha2.DeploymentSpec{
@@ -586,7 +586,7 @@ func TestTransformerToKnativeService(t *testing.T) {
 	}
 
 	isvcCanary := isvc.DeepCopy()
-	isvcCanary.Spec.CanaryTrafficPercent = 20
+	isvcCanary.Spec.CanaryTrafficPercent = v1alpha2.GetIntReference(20)
 	isvcCanary.Spec.Canary = &v1alpha2.EndpointSpec{
 		Transformer: &v1alpha2.TransformerSpec{
 			DeploymentSpec: v1alpha2.DeploymentSpec{
@@ -785,7 +785,7 @@ func TestExplainerToKnativeService(t *testing.T) {
 	}
 
 	isvcCanary := isvc.DeepCopy()
-	isvcCanary.Spec.CanaryTrafficPercent = 20
+	isvcCanary.Spec.CanaryTrafficPercent = v1alpha2.GetIntReference(20)
 	isvcCanary.Spec.Canary = &v1alpha2.EndpointSpec{
 		Predictor: v1alpha2.PredictorSpec{
 			DeploymentSpec: v1alpha2.DeploymentSpec{
diff --git a/pkg/controller/inferenceservice/resources/knative/suite_test.go b/pkg/controller/v1alpha2/inferenceservice/resources/knative/suite_test.go
similarity index 100%
rename from pkg/controller/inferenceservice/resources/knative/suite_test.go
rename to pkg/controller/v1alpha2/inferenceservice/resources/knative/suite_test.go
diff --git a/pkg/controller/inferenceservice/suite_test.go b/pkg/controller/v1alpha2/inferenceservice/suite_test.go
similarity index 100%
rename from pkg/controller/inferenceservice/suite_test.go
rename to pkg/controller/v1alpha2/inferenceservice/suite_test.go
diff --git a/pkg/controller/v1beta1/inferenceservice/controller_test.go b/pkg/controller/v1beta1/inferenceservice/controller_test.go
new file mode 100644
--- /dev/null
+++ b/pkg/controller/v1beta1/inferenceservice/controller_test.go
@@ -0,0 +1,865 @@
+/*
+Copyright 2020 kubeflow.org.
+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 inferenceservice
+
+import (
+	"context"
+	"github.com/golang/protobuf/proto"
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	"github.com/kubeflow/kfserving/pkg/apis/serving/v1beta1"
+	"github.com/kubeflow/kfserving/pkg/constants"
+	. "github.com/onsi/ginkgo"
+	"github.com/onsi/gomega"
+	. "github.com/onsi/gomega"
+	istiov1alpha3 "istio.io/api/networking/v1alpha3"
+	"istio.io/client-go/pkg/apis/networking/v1alpha3"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/util/retry"
+	"knative.dev/pkg/apis"
+	duckv1 "knative.dev/pkg/apis/duck/v1"
+	"knative.dev/pkg/network"
+	knservingv1 "knative.dev/serving/pkg/apis/serving/v1"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+	"time"
+)
+
+var _ = Describe("v1beta1 inference service controller", func() {
+	// Define utility constants for object names and testing timeouts/durations and intervals.
+	const (
+		timeout  = time.Second * 10
+		interval = time.Millisecond * 250
+		domain   = "example.com"
+	)
+	var (
+		defaultResource = v1.ResourceRequirements{
+			Limits: v1.ResourceList{
+				v1.ResourceCPU:    resource.MustParse("1"),
+				v1.ResourceMemory: resource.MustParse("2Gi"),
+			},
+			Requests: v1.ResourceList{
+				v1.ResourceCPU:    resource.MustParse("1"),
+				v1.ResourceMemory: resource.MustParse("2Gi"),
+			},
+		}
+		configs = map[string]string{
+			"predictors": `{
+               "tensorflow": {
+                  "image": "tensorflow/serving"
+               },
+               "sklearn": {
+                  "image": "kfserving/sklearnserver"
+               },
+               "xgboost": {
+                  "image": "kfserving/xgbserver"
+               }
+	         }`,
+			"explainers": `{
+               "alibi": {
+                  "image": "kfserving/alibi-explainer",
+			      "defaultImageVersion": "latest"
+               }
+            }`,
+			"ingress": `{
+               "ingressGateway": "knative-serving/knative-ingress-gateway",
+               "ingressService": "test-destination"
+            }`,
+		}
+	)
+	Context("When creating inference service with predictor", func() {
+		It("Should have knative service created", func() {
+			By("By creating a new InferenceService")
+			// Create configmap
+			var configMap = &v1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      constants.InferenceServiceConfigMapName,
+					Namespace: constants.KFServingNamespace,
+				},
+				Data: configs,
+			}
+			Expect(k8sClient.Create(context.TODO(), configMap)).NotTo(HaveOccurred())
+			defer k8sClient.Delete(context.TODO(), configMap)
+
+			serviceName := "foo"
+			var expectedRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: serviceName, Namespace: "default"}}
+			var serviceKey = expectedRequest.NamespacedName
+			var storageUri = "s3://test/mnist/export"
+			ctx := context.Background()
+			isvc := &v1beta1.InferenceService{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      serviceKey.Name,
+					Namespace: serviceKey.Namespace,
+				},
+				Spec: v1beta1.InferenceServiceSpec{
+					Predictor: v1beta1.PredictorSpec{
+						ComponentExtensionSpec: v1beta1.ComponentExtensionSpec{
+							MinReplicas: v1beta1.GetIntReference(1),
+							MaxReplicas: 3,
+						},
+						Tensorflow: &v1beta1.TFServingSpec{
+							PredictorExtensionSpec: v1beta1.PredictorExtensionSpec{
+								StorageURI:     &storageUri,
+								RuntimeVersion: proto.String("1.14.0"),
+								Container: v1.Container{
+									Name:      "kfs",
+									Resources: defaultResource,
+								},
+							},
+						},
+					},
+				},
+			}
+			Expect(k8sClient.Create(ctx, isvc)).Should(Succeed())
+			inferenceService := &v1beta1.InferenceService{}
+
+			Eventually(func() bool {
+				err := k8sClient.Get(ctx, serviceKey, inferenceService)
+				if err != nil {
+					return false
+				}
+				return true
+			}, timeout, interval).Should(BeTrue())
+
+			actualService := &knservingv1.Service{}
+			predictorServiceKey := types.NamespacedName{Name: constants.DefaultPredictorServiceName(serviceKey.Name),
+				Namespace: serviceKey.Namespace}
+			Eventually(func() error { return k8sClient.Get(context.TODO(), predictorServiceKey, actualService) }, timeout).
+				Should(Succeed())
+
+			expectedService := &knservingv1.Service{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      predictorServiceKey.Name,
+					Namespace: predictorServiceKey.Namespace,
+				},
+				Spec: knservingv1.ServiceSpec{
+					ConfigurationSpec: knservingv1.ConfigurationSpec{
+						Template: knservingv1.RevisionTemplateSpec{
+							ObjectMeta: metav1.ObjectMeta{
+								Labels: map[string]string{
+									constants.KServiceComponentLabel:      constants.Predictor.String(),
+									constants.InferenceServicePodLabelKey: serviceName,
+								},
+								Annotations: map[string]string{
+									"autoscaling.knative.dev/maxScale":                         "3",
+									"autoscaling.knative.dev/minScale":                         "1",
+									constants.StorageInitializerSourceUriInternalAnnotationKey: *isvc.Spec.Predictor.Tensorflow.StorageURI,
+									"autoscaling.knative.dev/class":                            "kpa.autoscaling.knative.dev",
+								},
+							},
+							Spec: knservingv1.RevisionSpec{
+								ContainerConcurrency: isvc.Spec.Predictor.ContainerConcurrency,
+								TimeoutSeconds:       isvc.Spec.Predictor.TimeoutSeconds,
+								PodSpec: v1.PodSpec{
+									Containers: []v1.Container{
+										{
+											Image: "tensorflow/serving:" +
+												*isvc.Spec.Predictor.Tensorflow.RuntimeVersion,
+											Name:    constants.InferenceServiceContainerName,
+											Command: []string{v1beta1.TensorflowEntrypointCommand},
+											Args: []string{
+												"--port=" + v1beta1.TensorflowServingGRPCPort,
+												"--rest_api_port=" + v1beta1.TensorflowServingRestPort,
+												"--model_name=" + isvc.Name,
+												"--model_base_path=" + constants.DefaultModelLocalMountPath,
+											},
+											Resources: defaultResource,
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			}
+			expectedService.SetDefaults(context.TODO())
+			Expect(actualService.Spec.ConfigurationSpec).To(gomega.Equal(expectedService.Spec.ConfigurationSpec))
+			predictorUrl, _ := apis.ParseURL("http://" + constants.InferenceServiceHostName(constants.DefaultPredictorServiceName(serviceKey.Name), serviceKey.Namespace, domain))
+			// update predictor
+			{
+				updatedService := actualService.DeepCopy()
+				updatedService.Status.LatestCreatedRevisionName = "revision-v1"
+				updatedService.Status.LatestReadyRevisionName = "revision-v1"
+				updatedService.Status.URL = predictorUrl
+				updatedService.Status.Conditions = duckv1.Conditions{
+					{
+						Type:   knservingv1.ServiceConditionReady,
+						Status: "True",
+					},
+				}
+				Expect(k8sClient.Status().Update(context.TODO(), updatedService)).NotTo(gomega.HaveOccurred())
+			}
+			//assert ingress
+			virtualService := &v1alpha3.VirtualService{}
+			Eventually(func() error {
+				return k8sClient.Get(context.TODO(), types.NamespacedName{Name: serviceKey.Name,
+					Namespace: serviceKey.Namespace}, virtualService)
+			}, timeout).
+				Should(gomega.Succeed())
+			expectedVirtualService := &v1alpha3.VirtualService{
+				Spec: istiov1alpha3.VirtualService{
+					Gateways: []string{
+						constants.KnativeIngressGateway,
+						constants.KnativeLocalGateway,
+					},
+					Hosts: []string{
+						constants.InferenceServiceHostName(serviceKey.Name, serviceKey.Namespace, domain),
+						network.GetServiceHostname(serviceKey.Name, serviceKey.Namespace),
+					},
+					Http: []*istiov1alpha3.HTTPRoute{
+						{
+							Match: []*istiov1alpha3.HTTPMatchRequest{
+								{
+									Gateways: []string{constants.KnativeLocalGateway},
+									Authority: &istiov1alpha3.StringMatch{
+										MatchType: &istiov1alpha3.StringMatch_Regex{
+											Regex: constants.HostRegExp(network.GetServiceHostname(serviceKey.Name, serviceKey.Namespace)),
+										},
+									},
+								},
+								{
+									Gateways: []string{constants.KnativeIngressGateway},
+									Authority: &istiov1alpha3.StringMatch{
+										MatchType: &istiov1alpha3.StringMatch_Regex{
+											Regex: constants.HostRegExp(constants.InferenceServiceHostName(serviceKey.Name, serviceKey.Namespace, domain)),
+										},
+									},
+								},
+							},
+							Route: []*istiov1alpha3.HTTPRouteDestination{
+								{
+									Headers: &istiov1alpha3.Headers{
+										Request: &istiov1alpha3.Headers_HeaderOperations{
+											Set: map[string]string{
+												"Host": network.GetServiceHostname(constants.DefaultPredictorServiceName(serviceKey.Name), serviceKey.Namespace),
+											},
+										},
+									},
+									Destination: &istiov1alpha3.Destination{
+										Host: network.GetServiceHostname("cluster-local-gateway", "istio-system"),
+										Port: &istiov1alpha3.PortSelector{Number: constants.CommonDefaultHttpPort},
+									},
+								},
+							},
+						},
+					},
+				},
+			}
+			Expect(virtualService.Spec).To(gomega.Equal(expectedVirtualService.Spec))
+		})
+	})
+
+	Context("Inference Service with transformer", func() {
+		It("Should create successfully", func() {
+			serviceName := "svc-with-transformer"
+			namespace := "default"
+			var expectedRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}}
+			var serviceKey = expectedRequest.NamespacedName
+
+			var predictorServiceKey = types.NamespacedName{Name: constants.DefaultPredictorServiceName(serviceName),
+				Namespace: namespace}
+			var transformerServiceKey = types.NamespacedName{Name: constants.DefaultTransformerServiceName(serviceName),
+				Namespace: namespace}
+			var transformer = &v1beta1.InferenceService{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      serviceName,
+					Namespace: namespace,
+				},
+				Spec: v1beta1.InferenceServiceSpec{
+					Predictor: v1beta1.PredictorSpec{
+						ComponentExtensionSpec: v1beta1.ComponentExtensionSpec{
+							MinReplicas: v1beta1.GetIntReference(1),
+							MaxReplicas: 3,
+						},
+						Tensorflow: &v1beta1.TFServingSpec{
+							PredictorExtensionSpec: v1beta1.PredictorExtensionSpec{
+								StorageURI:     proto.String("s3://test/mnist/export"),
+								RuntimeVersion: proto.String("1.13.0"),
+							},
+						},
+					},
+					Transformer: &v1beta1.TransformerSpec{
+						ComponentExtensionSpec: v1beta1.ComponentExtensionSpec{
+							MinReplicas: v1beta1.GetIntReference(1),
+							MaxReplicas: 3,
+						},
+						PodSpec: v1beta1.PodSpec{
+							Containers: []v1.Container{
+								{
+									Image:     "transformer:v1",
+									Resources: defaultResource,
+								},
+							},
+						},
+					},
+				},
+				Status: v1beta1.InferenceServiceStatus{
+					Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{
+						v1beta1.PredictorComponent: {
+							LatestReadyRevision: "revision-v1",
+						},
+					},
+				},
+			}
+
+			// Create configmap
+			var configMap = &v1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      constants.InferenceServiceConfigMapName,
+					Namespace: constants.KFServingNamespace,
+				},
+				Data: configs,
+			}
+			Expect(k8sClient.Create(context.TODO(), configMap)).NotTo(gomega.HaveOccurred())
+			defer k8sClient.Delete(context.TODO(), configMap)
+
+			// Create the InferenceService object and expect the Reconcile and knative service to be created
+			instance := transformer.DeepCopy()
+			Expect(k8sClient.Create(context.TODO(), instance)).NotTo(gomega.HaveOccurred())
+			defer k8sClient.Delete(context.TODO(), instance)
+
+			predictorService := &knservingv1.Service{}
+			Eventually(func() error { return k8sClient.Get(context.TODO(), predictorServiceKey, predictorService) }, timeout).
+				Should(gomega.Succeed())
+
+			transformerService := &knservingv1.Service{}
+			Eventually(func() error { return k8sClient.Get(context.TODO(), transformerServiceKey, transformerService) }, timeout).
+				Should(gomega.Succeed())
+
+			expectedTransformerService := &knservingv1.Service{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      constants.DefaultTransformerServiceName(instance.Name),
+					Namespace: instance.Namespace,
+				},
+				Spec: knservingv1.ServiceSpec{
+					ConfigurationSpec: knservingv1.ConfigurationSpec{
+						Template: knservingv1.RevisionTemplateSpec{
+							ObjectMeta: metav1.ObjectMeta{
+								Labels: map[string]string{"serving.kubeflow.org/inferenceservice": serviceName,
+									constants.KServiceComponentLabel: constants.Transformer.String(),
+								},
+								Annotations: map[string]string{
+									"autoscaling.knative.dev/class":    "kpa.autoscaling.knative.dev",
+									"autoscaling.knative.dev/maxScale": "3",
+									"autoscaling.knative.dev/minScale": "1",
+								},
+							},
+							Spec: knservingv1.RevisionSpec{
+								ContainerConcurrency: nil,
+								TimeoutSeconds:       nil,
+								PodSpec: v1.PodSpec{
+									Containers: []v1.Container{
+										{
+											Image: "transformer:v1",
+											Args: []string{
+												"--model_name",
+												serviceName,
+												"--predictor_host",
+												constants.DefaultPredictorServiceName(instance.Name) + "." + instance.Namespace,
+												constants.ArgumentHttpPort,
+												constants.InferenceServiceDefaultHttpPort,
+											},
+											Resources: defaultResource,
+										},
+									},
+								},
+							},
+						},
+					},
+					RouteSpec: knservingv1.RouteSpec{
+						Traffic: []knservingv1.TrafficTarget{{Tag: "latest", LatestRevision: proto.Bool(true), Percent: proto.Int64(100)}},
+					},
+				},
+			}
+			expectedTransformerService.SetDefaults(context.TODO())
+			Expect(cmp.Diff(transformerService.Spec, expectedTransformerService.Spec)).To(gomega.Equal(""))
+
+			// mock update knative service status since knative serving controller is not running in test
+			predictorUrl, _ := apis.ParseURL("http://" + constants.InferenceServiceHostName(constants.DefaultPredictorServiceName(serviceKey.Name), serviceKey.Namespace, domain))
+			transformerUrl, _ := apis.ParseURL("http://" + constants.InferenceServiceHostName(constants.DefaultTransformerServiceName(serviceKey.Name), serviceKey.Namespace, domain))
+
+			// update predictor
+			updatedPredictorService := predictorService.DeepCopy()
+			updatedPredictorService.Status.LatestCreatedRevisionName = "revision-v1"
+			updatedPredictorService.Status.LatestReadyRevisionName = "revision-v1"
+			updatedPredictorService.Status.URL = predictorUrl
+			updatedPredictorService.Status.Conditions = duckv1.Conditions{
+				{
+					Type:   knservingv1.ServiceConditionReady,
+					Status: "True",
+				},
+			}
+			Expect(k8sClient.Status().Update(context.TODO(), updatedPredictorService)).NotTo(gomega.HaveOccurred())
+
+			// update transformer
+			updatedTransformerService := transformerService.DeepCopy()
+			updatedTransformerService.Status.LatestCreatedRevisionName = "t-revision-v1"
+			updatedTransformerService.Status.LatestReadyRevisionName = "t-revision-v1"
+			updatedTransformerService.Status.URL = transformerUrl
+			updatedTransformerService.Status.Conditions = duckv1.Conditions{
+				{
+					Type:   knservingv1.ServiceConditionReady,
+					Status: "True",
+				},
+			}
+			Expect(k8sClient.Status().Update(context.TODO(), updatedTransformerService)).NotTo(gomega.HaveOccurred())
+
+			// verify if InferenceService status is updated
+			expectedIsvcStatus := v1beta1.InferenceServiceStatus{
+				Status: duckv1.Status{
+					Conditions: duckv1.Conditions{
+						{
+							Type:   v1beta1.IngressReady,
+							Status: "True",
+						},
+						{
+							Type:   v1beta1.PredictorReady,
+							Status: "True",
+						},
+						{
+							Type:   apis.ConditionReady,
+							Status: "True",
+						},
+						{
+							Type:     v1beta1.TransformerReady,
+							Severity: "Info",
+							Status:   "True",
+						},
+					},
+				},
+				URL: &apis.URL{
+					Scheme: "http",
+					Host:   constants.InferenceServiceHostName(serviceKey.Name, serviceKey.Namespace, domain),
+				},
+				Address: &duckv1.Addressable{
+					URL: &apis.URL{
+						Scheme: "http",
+						Host:   network.GetServiceHostname(serviceKey.Name, serviceKey.Namespace),
+					},
+				},
+				Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{
+					v1beta1.PredictorComponent: {
+						LatestReadyRevision:   "revision-v1",
+						LatestCreatedRevision: "revision-v1",
+						URL:                   predictorUrl,
+					},
+					v1beta1.TransformerComponent: {
+						LatestReadyRevision:   "t-revision-v1",
+						LatestCreatedRevision: "t-revision-v1",
+						URL:                   transformerUrl,
+					},
+				},
+			}
+			Eventually(func() string {
+				isvc := &v1beta1.InferenceService{}
+				if err := k8sClient.Get(context.TODO(), serviceKey, isvc); err != nil {
+					return err.Error()
+				}
+				return cmp.Diff(&expectedIsvcStatus, &isvc.Status, cmpopts.IgnoreTypes(apis.VolatileTime{}))
+			}, timeout).Should(gomega.BeEmpty())
+		})
+	})
+
+	Context("Inference Service with explainer", func() {
+		It("Should create successfully", func() {
+			serviceName := "svc-with-explainer"
+			namespace := "default"
+			var expectedRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}}
+			var serviceKey = expectedRequest.NamespacedName
+
+			var predictorServiceKey = types.NamespacedName{Name: constants.DefaultPredictorServiceName(serviceName),
+				Namespace: namespace}
+			var explainerServiceKey = types.NamespacedName{Name: constants.DefaultExplainerServiceName(serviceName),
+				Namespace: namespace}
+			var transformer = &v1beta1.InferenceService{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      serviceName,
+					Namespace: namespace,
+				},
+				Spec: v1beta1.InferenceServiceSpec{
+					Predictor: v1beta1.PredictorSpec{
+						ComponentExtensionSpec: v1beta1.ComponentExtensionSpec{
+							MinReplicas: v1beta1.GetIntReference(1),
+							MaxReplicas: 3,
+						},
+						Tensorflow: &v1beta1.TFServingSpec{
+							PredictorExtensionSpec: v1beta1.PredictorExtensionSpec{
+								StorageURI:     proto.String("s3://test/mnist/export"),
+								RuntimeVersion: proto.String("1.13.0"),
+							},
+						},
+					},
+					Explainer: &v1beta1.ExplainerSpec{
+						ComponentExtensionSpec: v1beta1.ComponentExtensionSpec{
+							MinReplicas: v1beta1.GetIntReference(1),
+							MaxReplicas: 3,
+						},
+						Alibi: &v1beta1.AlibiExplainerSpec{
+							Type:           v1beta1.AlibiAnchorsTabularExplainer,
+							StorageURI:     "s3://test/mnist/explainer",
+							RuntimeVersion: proto.String("0.4.0"),
+							Container: v1.Container{
+								Name:      "kfserving-contaienr",
+								Resources: defaultResource,
+							},
+						},
+					},
+				},
+				Status: v1beta1.InferenceServiceStatus{
+					Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{
+						v1beta1.PredictorComponent: {
+							LatestReadyRevision: "revision-v1",
+						},
+					},
+				},
+			}
+
+			// Create configmap
+			var configMap = &v1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      constants.InferenceServiceConfigMapName,
+					Namespace: constants.KFServingNamespace,
+				},
+				Data: configs,
+			}
+			Expect(k8sClient.Create(context.TODO(), configMap)).NotTo(gomega.HaveOccurred())
+			defer k8sClient.Delete(context.TODO(), configMap)
+
+			// Create the InferenceService object and expect the Reconcile and knative service to be created
+			instance := transformer.DeepCopy()
+			Expect(k8sClient.Create(context.TODO(), instance)).NotTo(gomega.HaveOccurred())
+			defer k8sClient.Delete(context.TODO(), instance)
+
+			predictorService := &knservingv1.Service{}
+			Eventually(func() error { return k8sClient.Get(context.TODO(), predictorServiceKey, predictorService) }, timeout).
+				Should(gomega.Succeed())
+
+			explainerService := &knservingv1.Service{}
+			Eventually(func() error { return k8sClient.Get(context.TODO(), explainerServiceKey, explainerService) }, timeout).
+				Should(gomega.Succeed())
+
+			expectedExplainerService := &knservingv1.Service{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      constants.DefaultTransformerServiceName(instance.Name),
+					Namespace: instance.Namespace,
+				},
+				Spec: knservingv1.ServiceSpec{
+					ConfigurationSpec: knservingv1.ConfigurationSpec{
+						Template: knservingv1.RevisionTemplateSpec{
+							ObjectMeta: metav1.ObjectMeta{
+								Labels: map[string]string{"serving.kubeflow.org/inferenceservice": serviceName,
+									constants.KServiceComponentLabel: constants.Explainer.String(),
+								},
+								Annotations: map[string]string{
+									"autoscaling.knative.dev/class":                               "kpa.autoscaling.knative.dev",
+									"autoscaling.knative.dev/maxScale":                            "3",
+									"autoscaling.knative.dev/minScale":                            "1",
+									"internal.serving.kubeflow.org/storage-initializer-sourceuri": "s3://test/mnist/explainer",
+								},
+							},
+							Spec: knservingv1.RevisionSpec{
+								ContainerConcurrency: nil,
+								TimeoutSeconds:       nil,
+								PodSpec: v1.PodSpec{
+									Containers: []v1.Container{
+										{
+											Name:  "kfserving-container",
+											Image: "kfserving/alibi-explainer:0.4.0",
+											Args: []string{
+												"--model_name",
+												serviceName,
+												"--predictor_host",
+												constants.DefaultPredictorServiceName(instance.Name) + "." + instance.Namespace,
+												constants.ArgumentHttpPort,
+												constants.InferenceServiceDefaultHttpPort,
+												"--storage_uri",
+												"/mnt/models",
+												"AnchorTabular",
+											},
+											Resources: defaultResource,
+										},
+									},
+								},
+							},
+						},
+					},
+					RouteSpec: knservingv1.RouteSpec{
+						Traffic: []knservingv1.TrafficTarget{{Tag: "latest", LatestRevision: proto.Bool(true), Percent: proto.Int64(100)}},
+					},
+				},
+			}
+			expectedExplainerService.SetDefaults(context.TODO())
+			Expect(cmp.Diff(explainerService.Spec, expectedExplainerService.Spec)).To(gomega.Equal(""))
+
+			// mock update knative service status since knative serving controller is not running in test
+			predictorUrl, _ := apis.ParseURL("http://" + constants.InferenceServiceHostName(constants.DefaultPredictorServiceName(serviceKey.Name), serviceKey.Namespace, domain))
+			explainerUrl, _ := apis.ParseURL("http://" + constants.InferenceServiceHostName(constants.DefaultExplainerServiceName(serviceKey.Name), serviceKey.Namespace, domain))
+
+			// update predictor
+			updatedPredictorService := predictorService.DeepCopy()
+			updatedPredictorService.Status.LatestCreatedRevisionName = "revision-v1"
+			updatedPredictorService.Status.LatestReadyRevisionName = "revision-v1"
+			updatedPredictorService.Status.URL = predictorUrl
+			updatedPredictorService.Status.Conditions = duckv1.Conditions{
+				{
+					Type:   knservingv1.ServiceConditionReady,
+					Status: "True",
+				},
+			}
+			Expect(k8sClient.Status().Update(context.TODO(), updatedPredictorService)).NotTo(gomega.HaveOccurred())
+
+			// update explainer
+			updatedExplainerervice := explainerService.DeepCopy()
+			updatedExplainerervice.Status.LatestCreatedRevisionName = "exp-revision-v1"
+			updatedExplainerervice.Status.LatestReadyRevisionName = "exp-revision-v1"
+			updatedExplainerervice.Status.URL = explainerUrl
+			updatedExplainerervice.Status.Conditions = duckv1.Conditions{
+				{
+					Type:   knservingv1.ServiceConditionReady,
+					Status: "True",
+				},
+			}
+			Expect(k8sClient.Status().Update(context.TODO(), updatedExplainerervice)).NotTo(gomega.HaveOccurred())
+
+			// verify if InferenceService status is updated
+			expectedIsvcStatus := v1beta1.InferenceServiceStatus{
+				Status: duckv1.Status{
+					Conditions: duckv1.Conditions{
+						{
+							Type:     v1beta1.ExplainerReady,
+							Severity: "Info",
+							Status:   "True",
+						},
+						{
+							Type:   v1beta1.IngressReady,
+							Status: "True",
+						},
+						{
+							Type:   v1beta1.PredictorReady,
+							Status: "True",
+						},
+						{
+							Type:   apis.ConditionReady,
+							Status: "True",
+						},
+					},
+				},
+				URL: &apis.URL{
+					Scheme: "http",
+					Host:   constants.InferenceServiceHostName(serviceKey.Name, serviceKey.Namespace, domain),
+				},
+				Address: &duckv1.Addressable{
+					URL: &apis.URL{
+						Scheme: "http",
+						Host:   network.GetServiceHostname(serviceKey.Name, serviceKey.Namespace),
+					},
+				},
+				Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{
+					v1beta1.PredictorComponent: {
+						LatestReadyRevision:   "revision-v1",
+						LatestCreatedRevision: "revision-v1",
+						URL:                   predictorUrl,
+					},
+					v1beta1.ExplainerComponent: {
+						LatestReadyRevision:   "exp-revision-v1",
+						LatestCreatedRevision: "exp-revision-v1",
+						URL:                   explainerUrl,
+					},
+				},
+			}
+			Eventually(func() string {
+				isvc := &v1beta1.InferenceService{}
+				if err := k8sClient.Get(context.TODO(), serviceKey, isvc); err != nil {
+					return err.Error()
+				}
+				return cmp.Diff(&expectedIsvcStatus, &isvc.Status, cmpopts.IgnoreTypes(apis.VolatileTime{}))
+			}, timeout).Should(gomega.BeEmpty())
+		})
+	})
+
+	Context("When doing canary out with inference service", func() {
+		It("Should have traffic split between two revisions", func() {
+			By("By moving canary traffic percent to the latest revision")
+			// Create configmap
+			var configMap = &v1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      constants.InferenceServiceConfigMapName,
+					Namespace: constants.KFServingNamespace,
+				},
+				Data: configs,
+			}
+			Expect(k8sClient.Create(context.TODO(), configMap)).NotTo(HaveOccurred())
+			defer k8sClient.Delete(context.TODO(), configMap)
+
+			serviceName := "foo-canary"
+			var expectedRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: serviceName, Namespace: "default"}}
+			var serviceKey = expectedRequest.NamespacedName
+			var storageUri = "s3://test/mnist/export"
+			var storageUri2 = "s3://test/mnist/export/v2"
+			ctx := context.Background()
+			isvc := &v1beta1.InferenceService{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      serviceKey.Name,
+					Namespace: serviceKey.Namespace,
+				},
+				Spec: v1beta1.InferenceServiceSpec{
+					Predictor: v1beta1.PredictorSpec{
+						ComponentExtensionSpec: v1beta1.ComponentExtensionSpec{
+							MinReplicas: v1beta1.GetIntReference(1),
+							MaxReplicas: 3,
+						},
+						Tensorflow: &v1beta1.TFServingSpec{
+							PredictorExtensionSpec: v1beta1.PredictorExtensionSpec{
+								StorageURI:     &storageUri,
+								RuntimeVersion: proto.String("1.14.0"),
+								Container: v1.Container{
+									Name:      "kfs",
+									Resources: defaultResource,
+								},
+							},
+						},
+					},
+				},
+			}
+
+			Expect(k8sClient.Create(ctx, isvc)).Should(Succeed())
+			inferenceService := &v1beta1.InferenceService{}
+
+			Eventually(func() bool {
+				err := k8sClient.Get(ctx, serviceKey, inferenceService)
+				if err != nil {
+					return false
+				}
+				return true
+			}, timeout, interval).Should(BeTrue())
+
+			updatedService := &knservingv1.Service{}
+			predictorServiceKey := types.NamespacedName{Name: constants.DefaultPredictorServiceName(serviceKey.Name),
+				Namespace: serviceKey.Namespace}
+			Eventually(func() error { return k8sClient.Get(context.TODO(), predictorServiceKey, updatedService) }, timeout).
+				Should(Succeed())
+
+			predictorUrl, _ := apis.ParseURL("http://" + constants.InferenceServiceHostName(constants.DefaultPredictorServiceName(serviceKey.Name), serviceKey.Namespace, domain))
+			// update predictor status
+			updatedService.Status.LatestCreatedRevisionName = "revision-v1"
+			updatedService.Status.LatestReadyRevisionName = "revision-v1"
+			updatedService.Status.URL = predictorUrl
+			updatedService.Status.Conditions = duckv1.Conditions{
+				{
+					Type:   knservingv1.ServiceConditionReady,
+					Status: "True",
+				},
+			}
+			Expect(retry.RetryOnConflict(retry.DefaultRetry, func() error {
+				return k8sClient.Status().Update(context.TODO(), updatedService)
+			})).NotTo(gomega.HaveOccurred())
+
+			// assert inference service predictor status
+			Eventually(func() string {
+				err := k8sClient.Get(ctx, serviceKey, inferenceService)
+				if err != nil {
+					return ""
+				}
+				return inferenceService.Status.Components[v1beta1.PredictorComponent].LatestReadyRevision
+			}, timeout, interval).Should(Equal("revision-v1"))
+
+			// update canary traffic percent to 20%
+			updatedIsvc := inferenceService.DeepCopy()
+			updatedIsvc.Spec.Predictor.Tensorflow.StorageURI = &storageUri2
+			updatedIsvc.Spec.Predictor.CanaryTrafficPercent = proto.Int64(20)
+			Expect(k8sClient.Update(context.TODO(), updatedIsvc)).NotTo(gomega.HaveOccurred())
+
+			// update predictor status
+			canaryService := &knservingv1.Service{}
+			Eventually(func() string {
+				k8sClient.Get(context.TODO(), predictorServiceKey, canaryService)
+				return canaryService.Spec.Template.Annotations[constants.StorageInitializerSourceUriInternalAnnotationKey]
+			}, timeout).Should(Equal(storageUri2))
+			canaryService.Status.LatestCreatedRevisionName = "revision-v2"
+			canaryService.Status.LatestReadyRevisionName = "revision-v2"
+			canaryService.Status.URL = predictorUrl
+			canaryService.Status.Conditions = duckv1.Conditions{
+				{
+					Type:   knservingv1.ServiceConditionReady,
+					Status: "True",
+				},
+			}
+			Expect(retry.RetryOnConflict(retry.DefaultRetry, func() error {
+				return k8sClient.Status().Update(context.TODO(), canaryService)
+			})).NotTo(gomega.HaveOccurred())
+
+			// assert inference service predictor status
+			Eventually(func() string {
+				err := k8sClient.Get(ctx, serviceKey, inferenceService)
+				if err != nil {
+					return ""
+				}
+				return inferenceService.Status.Components[v1beta1.PredictorComponent].PreviousReadyRevision
+			}, timeout, interval).Should(Equal("revision-v1"))
+
+			expectedTrafficTarget := []knservingv1.TrafficTarget{
+				{
+					Tag:            "latest",
+					LatestRevision: proto.Bool(true),
+					Percent:        proto.Int64(20),
+				},
+				{
+					Tag:            "prev",
+					RevisionName:   "revision-v1",
+					LatestRevision: proto.Bool(false),
+					Percent:        proto.Int64(80),
+				},
+			}
+			Eventually(func() []knservingv1.TrafficTarget {
+				actualService := &knservingv1.Service{}
+				err := k8sClient.Get(context.TODO(), predictorServiceKey, actualService)
+				if err != nil {
+					return []knservingv1.TrafficTarget{}
+				} else {
+					return actualService.Spec.Traffic
+				}
+			}, timeout).Should(gomega.Equal(expectedTrafficTarget))
+
+			rolloutIsvc := &v1beta1.InferenceService{}
+			Eventually(func() string {
+				err := k8sClient.Get(ctx, serviceKey, rolloutIsvc)
+				if err != nil {
+					return ""
+				}
+				return inferenceService.Status.Components[v1beta1.PredictorComponent].LatestReadyRevision
+			}, timeout, interval).Should(Equal("revision-v2"))
+			// rollout canary
+			rolloutIsvc.Spec.Predictor.CanaryTrafficPercent = nil
+			Expect(k8sClient.Update(context.TODO(), rolloutIsvc)).NotTo(gomega.HaveOccurred())
+			expectedTrafficTarget = []knservingv1.TrafficTarget{
+				{
+					Tag:            "latest",
+					LatestRevision: proto.Bool(true),
+					Percent:        proto.Int64(100),
+				},
+			}
+			Eventually(func() []knservingv1.TrafficTarget {
+				actualService := &knservingv1.Service{}
+				err := k8sClient.Get(context.TODO(), predictorServiceKey, actualService)
+				if err != nil {
+					return []knservingv1.TrafficTarget{}
+				} else {
+					return actualService.Spec.Traffic
+				}
+			}, timeout).Should(gomega.Equal(expectedTrafficTarget))
+		})
+	})
+})
diff --git a/pkg/controller/v1beta1/inferenceservice/suite_test.go b/pkg/controller/v1beta1/inferenceservice/suite_test.go
new file mode 100644
--- /dev/null
+++ b/pkg/controller/v1beta1/inferenceservice/suite_test.go
@@ -0,0 +1,93 @@
+/*
+Copyright 2020 kubeflow.org.
+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 inferenceservice
+
+import (
+	pkgtest "github.com/kubeflow/kfserving/pkg/testing"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"testing"
+
+	"github.com/kubeflow/kfserving/pkg/apis/serving/v1beta1"
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	"k8s.io/client-go/kubernetes/scheme"
+	"k8s.io/client-go/rest"
+	knservingv1 "knative.dev/serving/pkg/apis/serving/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/envtest"
+	logf "sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+)
+
+// These tests use Ginkgo (BDD-style Go testing framework). Refer to
+// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
+
+var cfg *rest.Config
+var k8sClient client.Client
+var testEnv *envtest.Environment
+
+func TestV1beta1APIs(t *testing.T) {
+	RegisterFailHandler(Fail)
+
+	RunSpecsWithDefaultAndCustomReporters(t,
+		"v1beta1 Controller Suite",
+		[]Reporter{envtest.NewlineReporter{}})
+}
+
+var _ = BeforeSuite(func(done Done) {
+	logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
+
+	By("bootstrapping test environment")
+	testEnv = pkgtest.SetupEnvTest()
+	cfg, err := testEnv.Start()
+	Expect(err).ToNot(HaveOccurred())
+	Expect(cfg).ToNot(BeNil())
+
+	err = v1beta1.AddToScheme(scheme.Scheme)
+	Expect(err).NotTo(HaveOccurred())
+	err = knservingv1.AddToScheme(scheme.Scheme)
+	Expect(err).NotTo(HaveOccurred())
+
+	k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
+	Expect(err).ToNot(HaveOccurred())
+	Expect(k8sClient).ToNot(BeNil())
+
+	k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
+		Scheme:             scheme.Scheme,
+		MetricsBindAddress: "0",
+	})
+	Expect(err).ToNot(HaveOccurred())
+	err = (&InferenceServiceReconciler{
+		Client:   k8sManager.GetClient(),
+		Scheme:   scheme.Scheme,
+		Log:      ctrl.Log.WithName("V1beta1InferenceServiceController"),
+		Recorder: k8sManager.GetEventRecorderFor("V1beta1InferenceServiceController"),
+	}).SetupWithManager(k8sManager)
+	Expect(err).ToNot(HaveOccurred())
+	defer GinkgoRecover()
+	go func() {
+		err = k8sManager.Start(ctrl.SetupSignalHandler())
+		Expect(err).ToNot(HaveOccurred())
+	}()
+
+	k8sClient = k8sManager.GetClient()
+	Expect(k8sClient).ToNot(BeNil())
+	close(done)
+}, 60)
+
+var _ = AfterSuite(func() {
+	By("tearing down the test environment")
+	err := testEnv.Stop()
+	Expect(err).ToNot(HaveOccurred())
+})
diff --git a/pkg/modelconfig/configmap_test.go b/pkg/modelconfig/configmap_test.go
--- a/pkg/modelconfig/configmap_test.go
+++ b/pkg/modelconfig/configmap_test.go
@@ -1,3 +1,19 @@
+/*
+Copyright 2020 kubeflow.org.
+
+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 modelconfig
 
 import (
diff --git a/pkg/testing/envtest_setup.go b/pkg/testing/envtest_setup.go
--- a/pkg/testing/envtest_setup.go
+++ b/pkg/testing/envtest_setup.go
@@ -38,10 +38,10 @@ func SetupEnvTest() *envtest.Environment {
 		// The relative paths must be provided for each level of test nesting
 		// This code should be illegal
 		CRDDirectoryPaths: []string{
-			filepath.Join("..", "..", "..", "..", "..", "config", "crd"),
-			filepath.Join("..", "..", "..", "..", "..", "test", "crds"),
-			filepath.Join("..", "..", "..", "config", "crd"),
-			filepath.Join("..", "..", "..", "test", "crds"),
+			filepath.Join("..", "..", "..", "..", "..", "..", "config", "crd"),
+			filepath.Join("..", "..", "..", "..", "..", "..", "test", "crds"),
+			filepath.Join("..", "..", "..", "..", "config", "crd"),
+			filepath.Join("..", "..", "..", "..", "test", "crds"),
 		},
 		UseExistingCluster: proto.Bool(false),
 	}
diff --git a/python/kfserving/test/test_inference_service_client.py b/python/kfserving/test/test_inference_service_client.py
--- a/python/kfserving/test/test_inference_service_client.py
+++ b/python/kfserving/test/test_inference_service_client.py
@@ -95,11 +95,11 @@ def test_inferenceservice_client_patch():
             'flower-sample', isvc, namespace='kubeflow')
 
 
-def test_inferenceservice_client_promote():
+def test_inferenceservice_client_rollout_canary():
     '''Unit test for kfserving promote api'''
-    with patch('kfserving.api.kf_serving_client.KFServingClient.promote',
+    with patch('kfserving.api.kf_serving_client.KFServingClient.rollout_canary',
                return_value=mocked_unit_result):
-        assert mocked_unit_result == KFServing.promote(
+        assert mocked_unit_result == KFServing.rollout_canary(
             'flower-sample', namespace='kubeflow')
 
 
diff --git a/test/e2e/batcher/test_batcher.py b/test/e2e/batcher/test_batcher.py
--- a/test/e2e/batcher/test_batcher.py
+++ b/test/e2e/batcher/test_batcher.py
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
 from kubernetes import client
 
 from kfserving import KFServingClient
@@ -28,7 +29,7 @@
 from concurrent import futures
 
 api_version = constants.KFSERVING_GROUP + '/' + constants.KFSERVING_VERSION
-KFServing = KFServingClient(config_file="~/.kube/config")
+KFServing = KFServingClient(config_file=os.environ.get("KUBECONFIG","~/.kube/config"))
 
 
 def test_batcher():
@@ -60,7 +61,7 @@ def test_batcher():
         KFServing.wait_isvc_ready(service_name, namespace=KFSERVING_TEST_NAMESPACE)
     except RuntimeError as e:
         print(KFServing.api_instance.get_namespaced_custom_object("serving.knative.dev", "v1", KFSERVING_TEST_NAMESPACE,
-                                                                  "services", service_name + "-predictor-default"))
+                                                                  "services", service_name + "-predictor"))
         pods = KFServing.core_api.list_namespaced_pod(KFSERVING_TEST_NAMESPACE,
                                                       label_selector='serving.kubeflow.org/inferenceservice={}'.
                                                       format(service_name))
diff --git a/test/e2e/common/utils.py b/test/e2e/common/utils.py
--- a/test/e2e/common/utils.py
+++ b/test/e2e/common/utils.py
@@ -15,19 +15,21 @@
 import logging
 import json
 import requests
+import os
 from urllib.parse import urlparse
 from kubernetes import client, config
 from kfserving import KFServingClient
+from kfserving import constants
 
 logging.basicConfig(level=logging.INFO)
 
-KFServing = KFServingClient(config_file="~/.kube/config")
+KFServing = KFServingClient(config_file=os.environ.get("KUBECONFIG", "~/.kube/config"))
 KFSERVING_NAMESPACE = "kfserving-system"
 KFSERVING_TEST_NAMESPACE = "kfserving-ci-e2e-test"
 
 
 def predict(service_name, input_json):
-    isvc = KFServing.get(service_name, namespace=KFSERVING_TEST_NAMESPACE)
+    isvc = KFServing.get(service_name, namespace=KFSERVING_TEST_NAMESPACE, version=constants.KFSERVING_V1BETA1_VERSION)
     # temporary sleep until this is fixed https://github.com/kubeflow/kfserving/issues/604
     time.sleep(10)
     cluster_ip = get_cluster_ip()
@@ -44,7 +46,7 @@ def predict(service_name, input_json):
 
 
 def explain(service_name, input_json):
-    isvc = KFServing.get(service_name, namespace=KFSERVING_TEST_NAMESPACE)
+    isvc = KFServing.get(service_name, namespace=KFSERVING_TEST_NAMESPACE, version=constants.KFSERVING_V1BETA1_VERSION)
     # temporary sleep until this is fixed https://github.com/kubeflow/kfserving/issues/604
     time.sleep(10)
     cluster_ip = get_cluster_ip()
@@ -54,11 +56,6 @@ def explain(service_name, input_json):
     with open(input_json) as json_file:
         data = json.load(json_file)
         logging.info("Sending request data: %s", json.dumps(data))
-        logging.info("Print pod info before explain")
-        pods = KFServing.core_api.list_namespaced_pod(KFSERVING_TEST_NAMESPACE,
-               label_selector='serving.kubeflow.org/inferenceservice={}'.format(service_name))
-        for pod in pods.items:
-            logging.info(pod)
         try: 
             response = requests.post(url, json.dumps(data), headers=headers)
             logging.info("Got response code %s, content %s", response.status_code, response.content)
@@ -66,7 +63,7 @@ def explain(service_name, input_json):
         except (RuntimeError, json.decoder.JSONDecodeError) as e:
             logging.info("Explain error -------")
             logging.info(KFServing.api_instance.get_namespaced_custom_object("serving.knative.dev", "v1", KFSERVING_TEST_NAMESPACE,
-                                                                      "services", service_name + "-explainer-default"))
+                                                                      "services", service_name + "-explainer"))
             pods = KFServing.core_api.list_namespaced_pod(KFSERVING_TEST_NAMESPACE,
                                                         label_selector='serving.kubeflow.org/inferenceservice={}'.
                                                         format(service_name))
@@ -89,4 +86,4 @@ def get_cluster_ip():
         cluster_ip = service.spec.cluster_ip
     else:
         cluster_ip = service.status.load_balancer.ingress[0].ip
-    return cluster_ip
+    return os.environ.get("KFSERVING_INGRESS_HOST_PORT", cluster_ip)
diff --git a/test/e2e/explainer/test_tabular_explainer.py b/test/e2e/explainer/test_tabular_explainer.py
--- a/test/e2e/explainer/test_tabular_explainer.py
+++ b/test/e2e/explainer/test_tabular_explainer.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 import logging
-
+import os
 from kubernetes import client
 
 from kfserving import KFServingClient
@@ -33,7 +33,7 @@
 
 logging.basicConfig(level=logging.INFO)
 api_version = constants.KFSERVING_GROUP + '/' + constants.KFSERVING_VERSION
-KFServing = KFServingClient(config_file="~/.kube/config")
+KFServing = KFServingClient(config_file=os.environ.get("KUBECONFIG", "~/.kube/config"))
 
 
 def test_tabular_explainer():
@@ -47,7 +47,7 @@ def test_tabular_explainer():
                     limits={'cpu': '100m', 'memory': '1Gi'}))),
         explainer=V1alpha2ExplainerSpec(
             min_replicas=1,
-             alibi=V1alpha2AlibiExplainerSpec(
+            alibi=V1alpha2AlibiExplainerSpec(
                 type='AnchorTabular',
                 storage_uri='gs://seldon-models/sklearn/income/alibi/0.4.0',
                 resources=V1ResourceRequirements(
@@ -57,20 +57,20 @@ def test_tabular_explainer():
     isvc = V1alpha2InferenceService(api_version=api_version,
                                     kind=constants.KFSERVING_KIND,
                                     metadata=client.V1ObjectMeta(
-                                    name=service_name, namespace=KFSERVING_TEST_NAMESPACE),
+                                      name=service_name, namespace=KFSERVING_TEST_NAMESPACE),
                                     spec=V1alpha2InferenceServiceSpec(default=default_endpoint_spec))
 
     KFServing.create(isvc)
     try:
-       KFServing.wait_isvc_ready(service_name, namespace=KFSERVING_TEST_NAMESPACE, timeout_seconds=720)
+        KFServing.wait_isvc_ready(service_name, namespace=KFSERVING_TEST_NAMESPACE, timeout_seconds=300)
     except RuntimeError as e:
-       logging.info(KFServing.api_instance.get_namespaced_custom_object("serving.knative.dev", "v1", 
-           KFSERVING_TEST_NAMESPACE, "services", service_name + "-predictor-default"))
-       pods = KFServing.core_api.list_namespaced_pod(KFSERVING_TEST_NAMESPACE, 
+        logging.info(KFServing.api_instance.get_namespaced_custom_object("serving.knative.dev", "v1",
+           KFSERVING_TEST_NAMESPACE, "services", service_name + "-predictor"))
+        pods = KFServing.core_api.list_namespaced_pod(KFSERVING_TEST_NAMESPACE,
                label_selector='serving.kubeflow.org/inferenceservice={}'.format(service_name))
-       for pod in pods.items:
-           logging.info(pod)
-       raise e
+        for pod in pods.items:
+            logging.info(pod)
+        raise e
     
     res = predict(service_name, './data/income_input.json')
     assert(res["predictions"] == [0])
diff --git a/test/e2e/logger/test_logger.py b/test/e2e/logger/test_logger.py
--- a/test/e2e/logger/test_logger.py
+++ b/test/e2e/logger/test_logger.py
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
+import pytest
 from kubernetes import client
 
 from kfserving import KFServingClient
@@ -29,9 +31,10 @@
 from ..common.utils import KFSERVING_TEST_NAMESPACE
 
 api_version = constants.KFSERVING_GROUP + '/' + constants.KFSERVING_VERSION
-KFServing = KFServingClient(config_file="~/.kube/config")
+KFServing = KFServingClient(config_file=os.environ.get("KUBECONFIG","~/.kube/config"))
 
 
+@pytest.mark.skip(reason="reworking this in v1beta1")
 def test_kfserving_logger():
     msg_dumper = 'message-dumper'
     default_endpoint_spec = V1alpha2EndpointSpec(
@@ -58,7 +61,7 @@ def test_kfserving_logger():
             min_replicas=1,
             logger=V1alpha2Logger(
                mode="all",
-               url="http://message-dumper-predictor-default."+KFSERVING_TEST_NAMESPACE
+               url="http://message-dumper-predictor."+KFSERVING_TEST_NAMESPACE
             ),
             sklearn=V1alpha2SKLearnSpec(
                 storage_uri='gs://kfserving-samples/models/sklearn/iris',
diff --git a/test/e2e/predictor/test_canary.py b/test/e2e/predictor/test_canary.py
--- a/test/e2e/predictor/test_canary.py
+++ b/test/e2e/predictor/test_canary.py
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
 import numpy as np
 from kubernetes import client
 
@@ -29,7 +30,7 @@
 
 
 # Setting config_file is required since SDK is running in a different cluster than KFServing
-KFServing = KFServingClient(config_file="~/.kube/config")
+KFServing = KFServingClient(config_file=os.environ.get("KUBECONFIG", "~/.kube/config"))
 
 
 def test_canary_rollout():
@@ -62,11 +63,7 @@ def test_canary_rollout():
                     limits={'cpu':'100m', 'memory':'256Mi'}))))
 
     KFServing.rollout_canary(service_name, canary=canary_endpoint_spec, percent=10,
-       namespace=KFSERVING_TEST_NAMESPACE, watch=True, timeout_seconds=120)
-    KFServing.wait_isvc_ready(service_name, namespace=KFSERVING_TEST_NAMESPACE)
-
-    # Promote Canary to Default
-    KFServing.promote(service_name, namespace=KFSERVING_TEST_NAMESPACE, watch=True, timeout_seconds=120)
+                             namespace=KFSERVING_TEST_NAMESPACE, watch=True, timeout_seconds=120)
     KFServing.wait_isvc_ready(service_name, namespace=KFSERVING_TEST_NAMESPACE)
 
     # Delete the InferenceService
diff --git a/test/e2e/predictor/test_pytorch.py b/test/e2e/predictor/test_pytorch.py
--- a/test/e2e/predictor/test_pytorch.py
+++ b/test/e2e/predictor/test_pytorch.py
@@ -13,6 +13,8 @@
 # limitations under the License.
 
 import numpy as np
+import os
+import pytest
 from kubernetes import client
 
 from kfserving import KFServingClient
@@ -27,7 +29,7 @@
 from ..common.utils import KFSERVING_TEST_NAMESPACE
 
 api_version = constants.KFSERVING_GROUP + '/' + constants.KFSERVING_VERSION
-KFServing = KFServingClient(config_file="~/.kube/config")
+KFServing = KFServingClient(config_file=os.environ.get("KUBECONFIG", "~/.kube/config"))
 
 
 def test_pytorch():
@@ -35,6 +37,7 @@ def test_pytorch():
     default_endpoint_spec = V1alpha2EndpointSpec(
         predictor=V1alpha2PredictorSpec(
             min_replicas=1,
+            parallelism=1,
             pytorch=V1alpha2PyTorchSpec(
                 storage_uri='gs://kfserving-samples/models/pytorch/cifar10',
                 model_class_name="Net",
@@ -54,7 +57,7 @@ def test_pytorch():
         KFServing.wait_isvc_ready(service_name, namespace=KFSERVING_TEST_NAMESPACE)
     except RuntimeError as e:
         print(KFServing.api_instance.get_namespaced_custom_object("serving.knative.dev", "v1", KFSERVING_TEST_NAMESPACE,
-                                                                  "services", service_name + "-predictor-default"))
+                                                                  "services", service_name + "-predictor"))
         pods = KFServing.core_api.list_namespaced_pod(KFSERVING_TEST_NAMESPACE,
                                                       label_selector='serving.kubeflow.org/inferenceservice={}'.
                                                       format(service_name))
diff --git a/test/e2e/predictor/test_sklearn.py b/test/e2e/predictor/test_sklearn.py
--- a/test/e2e/predictor/test_sklearn.py
+++ b/test/e2e/predictor/test_sklearn.py
@@ -12,8 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
 from kubernetes import client
-
 from kfserving import KFServingClient
 from kfserving import constants
 from kfserving import V1alpha2EndpointSpec
@@ -27,7 +27,7 @@
 from ..common.utils import KFSERVING_TEST_NAMESPACE
 
 api_version = constants.KFSERVING_GROUP + '/' + constants.KFSERVING_VERSION
-KFServing = KFServingClient(config_file="~/.kube/config")
+KFServing = KFServingClient(config_file=os.environ.get("KUBECONFIG", "~/.kube/config"))
 
 
 def test_sklearn_kfserving():
diff --git a/test/e2e/predictor/test_tensorflow.py b/test/e2e/predictor/test_tensorflow.py
--- a/test/e2e/predictor/test_tensorflow.py
+++ b/test/e2e/predictor/test_tensorflow.py
@@ -12,9 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
 import numpy as np
 from kubernetes import client
-
 from kfserving import KFServingClient
 from kfserving import constants
 from kfserving import V1alpha2EndpointSpec
@@ -28,7 +28,7 @@
 from ..common.utils import KFSERVING_TEST_NAMESPACE
 
 api_version = constants.KFSERVING_GROUP + '/' + constants.KFSERVING_VERSION
-KFServing = KFServingClient(config_file="~/.kube/config")
+KFServing = KFServingClient(config_file=os.environ.get("KUBECONFIG", "~/.kube/config"))
 
 
 def test_tensorflow_kfserving():
diff --git a/test/e2e/predictor/test_triton.py b/test/e2e/predictor/test_triton.py
--- a/test/e2e/predictor/test_triton.py
+++ b/test/e2e/predictor/test_triton.py
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
 import numpy as np
 import pytest
 from kubernetes import client
@@ -32,7 +33,7 @@
 from ..common.utils import KFSERVING_TEST_NAMESPACE
 
 api_version = constants.KFSERVING_GROUP + '/' + constants.KFSERVING_VERSION
-KFServing = KFServingClient(config_file="~/.kube/config")
+KFServing = KFServingClient(config_file=os.environ.get("KUBECONFIG", "~/.kube/config"))
 
 
 def test_triton():
diff --git a/test/e2e/predictor/test_xgboost.py b/test/e2e/predictor/test_xgboost.py
--- a/test/e2e/predictor/test_xgboost.py
+++ b/test/e2e/predictor/test_xgboost.py
@@ -12,8 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
 from kubernetes import client
-
 from kfserving import KFServingClient
 from kfserving import constants
 from kfserving import V1alpha2EndpointSpec
@@ -27,7 +27,7 @@
 from ..common.utils import KFSERVING_TEST_NAMESPACE
 
 api_version = constants.KFSERVING_GROUP + '/' + constants.KFSERVING_VERSION
-KFServing = KFServingClient(config_file="~/.kube/config")
+KFServing = KFServingClient(config_file=os.environ.get("KUBECONFIG", "~/.kube/config"))
 
 
 def test_xgboost_kfserving():
diff --git a/test/e2e/transformer/test_transformer.py b/test/e2e/transformer/test_transformer.py
--- a/test/e2e/transformer/test_transformer.py
+++ b/test/e2e/transformer/test_transformer.py
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
 import numpy as np
 from kubernetes import client
 
@@ -30,7 +31,7 @@
 from ..common.utils import KFSERVING_TEST_NAMESPACE
 
 api_version = constants.KFSERVING_GROUP + '/' + constants.KFSERVING_VERSION
-KFServing = KFServingClient(config_file="~/.kube/config")
+KFServing = KFServingClient(config_file=os.environ.get("KUBECONFIG", "~/.kube/config"))
 
 
 def test_transformer():
@@ -65,7 +66,7 @@ def test_transformer():
         KFServing.wait_isvc_ready(service_name, namespace=KFSERVING_TEST_NAMESPACE)
     except RuntimeError as e:
         print(KFServing.api_instance.get_namespaced_custom_object("serving.knative.dev", "v1", KFSERVING_TEST_NAMESPACE,
-                                                                  "services", service_name + "-predictor-default"))
+                                                                  "services", service_name + "-predictor"))
         pods = KFServing.core_api.list_namespaced_pod(KFSERVING_TEST_NAMESPACE,
                                                       label_selector='serving.kubeflow.org/inferenceservice={}'
                                                       .format(service_name))