Repository: fluxcd/flagger
Pull request #765 (2020-12-22 15:11:33)

A/B testing support for Gloo Edge ingress controller
Resolves https://github.com/weaveworks/flagger/issues/707 , which is related to https://github.com/solo-io/gloo/issues/3726

The approach here is to replace Gloo `UpstreamGroup`s in the flagger implementation with Gloo `RouteTable`s, which additionally support header/method matching used in A/B testing. For this to work, we needed to add and leverage the new `inheritablePathMatcher` field on Gloo `RouteTable`s, thus these changes will only work in Gloo Edge 1.6.0-beta22 and higher. (Gloo Edge 1.6.0 release is slated for end of year)

Unit and integration tests all pass locally. The documented guide has been updated and confirmed as well.

Appreciate the attention to developer experience and developer guide, thanks!


Patch:
diff --git a/README.md b/README.md
index 6b27dbd5..3d45c4e6 100644
--- a/README.md
+++ b/README.md
@@ -201,7 +201,7 @@ For more details on how the canary analysis and promotion works please [read the
 | Feature                                    | Contour            | Gloo               | NGINX              | Skipper            | Traefik            |
 | ------------------------------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ |
 | Canary deployments (weighted traffic)      | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
-| A/B testing (headers and cookies routing)  | :heavy_check_mark: | :heavy_minus_sign: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: |
+| A/B testing (headers and cookies routing)  | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: |
 | Blue/Green deployments (traffic switch)    | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
 | Webhooks (acceptance/load testing)         | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
 | Manual gating (approve/pause/resume)       | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
diff --git a/artifacts/flagger/account.yaml b/artifacts/flagger/account.yaml
index 22c30447..c8ed9833 100644
--- a/artifacts/flagger/account.yaml
+++ b/artifacts/flagger/account.yaml
@@ -153,8 +153,19 @@ rules:
     resources:
       - upstreams
       - upstreams/finalizers
-      - upstreamgroups
-      - upstreamgroups/finalizers
+    verbs:
+      - get
+      - list
+      - watch
+      - create
+      - update
+      - patch
+      - delete
+  - apiGroups:
+      - gateway.solo.io
+    resources:
+      - routetables
+      - routetables/finalizers
     verbs:
       - get
       - list
diff --git a/charts/flagger/templates/rbac.yaml b/charts/flagger/templates/rbac.yaml
index 16a9151f..afd0d611 100644
--- a/charts/flagger/templates/rbac.yaml
+++ b/charts/flagger/templates/rbac.yaml
@@ -149,8 +149,19 @@ rules:
     resources:
       - upstreams
       - upstreams/finalizers
-      - upstreamgroups
-      - upstreamgroups/finalizers
+    verbs:
+      - get
+      - list
+      - watch
+      - create
+      - update
+      - patch
+      - delete
+  - apiGroups:
+      - gateway.solo.io
+    resources:
+      - routetables
+      - routetables/finalizers
     verbs:
       - get
       - list
diff --git a/docs/gitbook/tutorials/gloo-progressive-delivery.md b/docs/gitbook/tutorials/gloo-progressive-delivery.md
index 041de1b0..d5a32300 100644
--- a/docs/gitbook/tutorials/gloo-progressive-delivery.md
+++ b/docs/gitbook/tutorials/gloo-progressive-delivery.md
@@ -1,12 +1,15 @@
 # Gloo Canary Deployments
 
-This guide shows you how to use the [Gloo](https://gloo.solo.io/) ingress controller and Flagger to automate canary deployments.
+This guide shows you how to use the [Gloo Edge](https://gloo.solo.io/) ingress controller and Flagger to automate canary deployments.
 
 ![Flagger Gloo Ingress Controller](https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-gloo-overview.png)
 
 ## Prerequisites
 
-Flagger requires a Kubernetes cluster **v1.11** or newer and Gloo ingress **1.3.5** or newer.
+This guide was written for Flagger version **1.5.0** or higher. Prior versions of Flagger used Gloo upstream groups to handle
+canaries, but newer versions of Flagger use Gloo route tables to handle canaries as well as A/B testing.
+
+Flagger requires a Kubernetes cluster **v1.11** or newer and Gloo Edge ingress **1.6.0** or newer.
 
 Install Gloo with Helm v3:
 
@@ -30,7 +33,7 @@ helm upgrade -i flagger flagger/flagger \
 
 ## Bootstrap
 
-Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler \(HPA\), then creates a series of objects \(Kubernetes deployments, ClusterIP services and Gloo upstream groups\). These objects expose the application outside the cluster and drive the canary analysis and promotion.
+Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler \(HPA\), then creates a series of objects \(Kubernetes deployments, ClusterIP services and Gloo route tables groups\). These objects expose the application outside the cluster and drive the canary analysis and promotion.
 
 Create a test namespace:
 
@@ -50,7 +53,7 @@ Deploy the load testing service to generate traffic during the canary analysis:
 kubectl -n test apply -k github.com/weaveworks/flagger//kustomize/tester
 ```
 
-Create an virtual service definition that references an upstream group that will be generated by Flagger \(replace `app.example.com` with your own domain\):
+Create a virtual service definition that references a route table that will be generated by Flagger \(replace `app.example.com` with your own domain\):
 
 ```yaml
 apiVersion: gateway.solo.io/v1
@@ -65,8 +68,8 @@ spec:
     routes:
       - matchers:
          - prefix: /
-        routeAction:
-          upstreamGroup:
+        delegateAction:
+          ref:
             name: podinfo
             namespace: test
 ```
@@ -165,7 +168,7 @@ horizontalpodautoscaler.autoscaling/podinfo-primary
 service/podinfo
 service/podinfo-canary
 service/podinfo-primary
-upstreamgroups.gloo.solo.io/podinfo
+routetables.gateway.solo.io/podinfo
 ```
 
 When the bootstrap finishes Flagger will set the canary status to initialized:
@@ -247,13 +250,13 @@ podinfod=stefanprodan/podinfo:3.1.2
 Generate HTTP 500 errors:
 
 ```bash
-watch curl -H 'Host: app.example.com' http://gateway-proxy-v2.gloo-system/status/500
+watch curl -H 'Host: app.example.com' http://gateway-proxy.gloo-system/status/500
 ```
 
 Generate high latency:
 
 ```bash
-watch curl -H 'Host: app.example.com' http://gateway-proxy-v2.gloo-system/delay/2
+watch curl -H 'Host: app.example.com' http://gateway-proxy.gloo-system/delay/2
 ```
 
 When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, the canary is scaled to zero and the rollout is marked as failed.
@@ -345,7 +348,7 @@ podinfod=stefanprodan/podinfo:3.1.3
 Generate 404s:
 
 ```bash
-watch curl -H 'Host: app.example.com' http://gateway-proxy.gloo-system/status/400
+watch curl -H 'Host: app.example.com' http://gateway-proxy.gloo-system/status/404
 ```
 
 Watch Flagger logs:
diff --git a/docs/gitbook/usage/deployment-strategies.md b/docs/gitbook/usage/deployment-strategies.md
index 5835cf90..2b42bbbe 100644
--- a/docs/gitbook/usage/deployment-strategies.md
+++ b/docs/gitbook/usage/deployment-strategies.md
@@ -3,11 +3,11 @@
 Flagger can run automated application analysis, promotion and rollback for the following deployment strategies:
 
 * **Canary Release** \(progressive traffic shifting\)
-  * Istio, Linkerd, App Mesh, NGINX, Skipper, Contour, Gloo, Traefik
+  * Istio, Linkerd, App Mesh, NGINX, Skipper, Contour, Gloo Edge, Traefik
 * **A/B Testing** \(HTTP headers and cookies traffic routing\)
-  * Istio, App Mesh, NGINX, Contour
+  * Istio, App Mesh, NGINX, Contour, Gloo Edge
 * **Blue/Green** \(traffic switching\)
-  * Kubernetes CNI, Istio, Linkerd, App Mesh, NGINX, Contour, Gloo
+  * Kubernetes CNI, Istio, Linkerd, App Mesh, NGINX, Contour, Gloo Edge
 * **Blue/Green Mirroring** \(traffic shadowing\)
   * Istio
 
diff --git a/kustomize/base/flagger/rbac.yaml b/kustomize/base/flagger/rbac.yaml
index e2a6b48d..f4db1403 100644
--- a/kustomize/base/flagger/rbac.yaml
+++ b/kustomize/base/flagger/rbac.yaml
@@ -143,8 +143,19 @@ rules:
     resources:
       - upstreams
       - upstreams/finalizers
-      - upstreamgroups
-      - upstreamgroups/finalizers
+    verbs:
+      - get
+      - list
+      - watch
+      - create
+      - update
+      - patch
+      - delete
+  - apiGroups:
+      - gateway.solo.io
+    resources:
+      - routetables
+      - routetables/finalizers
     verbs:
       - get
       - list
diff --git a/pkg/apis/gloo/register.go b/pkg/apis/gloo/register.go
index 1e6e9016..0d717507 100644
--- a/pkg/apis/gloo/register.go
+++ b/pkg/apis/gloo/register.go
@@ -1,5 +1,5 @@
 package gloo
 
 const (
-	GroupName = "gloo.solo.io"
+	GroupName = "gateway.solo.io"
 )
diff --git a/pkg/apis/gloo/v1/doc.go b/pkg/apis/gloo/v1/doc.go
index 0c641332..75ed100f 100644
--- a/pkg/apis/gloo/v1/doc.go
+++ b/pkg/apis/gloo/v1/doc.go
@@ -1,5 +1,5 @@
 // +k8s:deepcopy-gen=package
 
 // Package v1 is the v1 version of the API.
-// +groupName=gloo.solo.io
+// +groupName=gateway.solo.io
 package v1
diff --git a/pkg/apis/gloo/v1/register.go b/pkg/apis/gloo/v1/register.go
index d06961fb..46c167ae 100755
--- a/pkg/apis/gloo/v1/register.go
+++ b/pkg/apis/gloo/v1/register.go
@@ -28,8 +28,8 @@ var (
 // Adds the list of known types to Scheme.
 func addKnownTypes(scheme *runtime.Scheme) error {
 	scheme.AddKnownTypes(SchemeGroupVersion,
-		&UpstreamGroup{},
-		&UpstreamGroupList{},
+		&RouteTable{},
+		&RouteTableList{},
 	)
 	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
 	return nil
diff --git a/pkg/apis/gloo/v1/types.go b/pkg/apis/gloo/v1/types.go
index 5209206c..c9f54abc 100644
--- a/pkg/apis/gloo/v1/types.go
+++ b/pkg/apis/gloo/v1/types.go
@@ -7,15 +7,48 @@ import (
 // +genclient
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
-// UpstreamGroup is a specification for a Gloo UpstreamGroup resource
-type UpstreamGroup struct {
+// RouteTable is a specification for a Gloo RouteTable resource
+type RouteTable struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata,omitempty"`
 
-	Spec UpstreamGroupSpec `json:"spec"`
+	Spec RouteTableSpec `json:"spec"`
 }
 
-type UpstreamGroupSpec struct {
+type RouteTableSpec struct {
+	Routes []Route `json:"routes,omitempty"`
+}
+
+type Route struct {
+	Matchers                []Matcher   `json:"matchers,omitempty"`
+	Action                  RouteAction `json:"routeAction,omitempty"`
+	InheritablePathMatchers bool        `json:"inheritablePathMatchers,omitempty"`
+}
+
+type Matcher struct {
+	Headers                []HeaderMatcher         `json:"headers,omitempty"`
+	QueryParameterMatchers []QueryParameterMatcher `json:"queryParameters,omitempty"`
+	Methods                []string                `json:"methods,omitempty"`
+}
+
+type HeaderMatcher struct {
+	Name        string `json:"name,omitempty"`
+	Value       string `json:"value,omitempty"`
+	Regex       bool   `json:"regex,omitempty"`
+	InvertMatch bool   `json:"invertMatch,omitempty"`
+}
+
+type QueryParameterMatcher struct {
+	Name  string `json:"name,omitempty"`
+	Value string `json:"value,omitempty"`
+	Regex bool   `json:"regex,omitempty"`
+}
+
+type RouteAction struct {
+	Destination MultiDestination `json:"multi,omitempty"`
+}
+
+type MultiDestination struct {
 	Destinations []WeightedDestination `json:"destinations,omitempty"`
 }
 
@@ -40,10 +73,10 @@ type ResourceRef struct {
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
-// UpstreamGroupList is a list of UpstreamGroup resources
-type UpstreamGroupList struct {
+// RouteTableList is a list of RouteTable resources
+type RouteTableList struct {
 	metav1.TypeMeta `json:",inline"`
 	metav1.ListMeta `json:"metadata"`
 
-	Items []UpstreamGroup `json:"items"`
+	Items []RouteTable `json:"items"`
 }
diff --git a/pkg/apis/gloo/v1/zz_generated.deepcopy.go b/pkg/apis/gloo/v1/zz_generated.deepcopy.go
index db498aef..44a00d9a 100644
--- a/pkg/apis/gloo/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/gloo/v1/zz_generated.deepcopy.go
@@ -41,6 +41,90 @@ func (in *Destination) DeepCopy() *Destination {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HeaderMatcher) DeepCopyInto(out *HeaderMatcher) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderMatcher.
+func (in *HeaderMatcher) DeepCopy() *HeaderMatcher {
+	if in == nil {
+		return nil
+	}
+	out := new(HeaderMatcher)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Matcher) DeepCopyInto(out *Matcher) {
+	*out = *in
+	if in.Headers != nil {
+		in, out := &in.Headers, &out.Headers
+		*out = make([]HeaderMatcher, len(*in))
+		copy(*out, *in)
+	}
+	if in.QueryParameterMatchers != nil {
+		in, out := &in.QueryParameterMatchers, &out.QueryParameterMatchers
+		*out = make([]QueryParameterMatcher, len(*in))
+		copy(*out, *in)
+	}
+	if in.Methods != nil {
+		in, out := &in.Methods, &out.Methods
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Matcher.
+func (in *Matcher) DeepCopy() *Matcher {
+	if in == nil {
+		return nil
+	}
+	out := new(Matcher)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *MultiDestination) DeepCopyInto(out *MultiDestination) {
+	*out = *in
+	if in.Destinations != nil {
+		in, out := &in.Destinations, &out.Destinations
+		*out = make([]WeightedDestination, len(*in))
+		copy(*out, *in)
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MultiDestination.
+func (in *MultiDestination) DeepCopy() *MultiDestination {
+	if in == nil {
+		return nil
+	}
+	out := new(MultiDestination)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *QueryParameterMatcher) DeepCopyInto(out *QueryParameterMatcher) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QueryParameterMatcher.
+func (in *QueryParameterMatcher) DeepCopy() *QueryParameterMatcher {
+	if in == nil {
+		return nil
+	}
+	out := new(QueryParameterMatcher)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ResourceRef) DeepCopyInto(out *ResourceRef) {
 	*out = *in
@@ -58,7 +142,48 @@ func (in *ResourceRef) DeepCopy() *ResourceRef {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *UpstreamGroup) DeepCopyInto(out *UpstreamGroup) {
+func (in *Route) DeepCopyInto(out *Route) {
+	*out = *in
+	if in.Matchers != nil {
+		in, out := &in.Matchers, &out.Matchers
+		*out = make([]Matcher, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	in.Action.DeepCopyInto(&out.Action)
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route.
+func (in *Route) DeepCopy() *Route {
+	if in == nil {
+		return nil
+	}
+	out := new(Route)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RouteAction) DeepCopyInto(out *RouteAction) {
+	*out = *in
+	in.Destination.DeepCopyInto(&out.Destination)
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteAction.
+func (in *RouteAction) DeepCopy() *RouteAction {
+	if in == nil {
+		return nil
+	}
+	out := new(RouteAction)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RouteTable) DeepCopyInto(out *RouteTable) {
 	*out = *in
 	out.TypeMeta = in.TypeMeta
 	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
@@ -66,18 +191,18 @@ func (in *UpstreamGroup) DeepCopyInto(out *UpstreamGroup) {
 	return
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpstreamGroup.
-func (in *UpstreamGroup) DeepCopy() *UpstreamGroup {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTable.
+func (in *RouteTable) DeepCopy() *RouteTable {
 	if in == nil {
 		return nil
 	}
-	out := new(UpstreamGroup)
+	out := new(RouteTable)
 	in.DeepCopyInto(out)
 	return out
 }
 
 // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *UpstreamGroup) DeepCopyObject() runtime.Object {
+func (in *RouteTable) DeepCopyObject() runtime.Object {
 	if c := in.DeepCopy(); c != nil {
 		return c
 	}
@@ -85,13 +210,13 @@ func (in *UpstreamGroup) DeepCopyObject() runtime.Object {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *UpstreamGroupList) DeepCopyInto(out *UpstreamGroupList) {
+func (in *RouteTableList) DeepCopyInto(out *RouteTableList) {
 	*out = *in
 	out.TypeMeta = in.TypeMeta
 	in.ListMeta.DeepCopyInto(&out.ListMeta)
 	if in.Items != nil {
 		in, out := &in.Items, &out.Items
-		*out = make([]UpstreamGroup, len(*in))
+		*out = make([]RouteTable, len(*in))
 		for i := range *in {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
@@ -99,18 +224,18 @@ func (in *UpstreamGroupList) DeepCopyInto(out *UpstreamGroupList) {
 	return
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpstreamGroupList.
-func (in *UpstreamGroupList) DeepCopy() *UpstreamGroupList {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTableList.
+func (in *RouteTableList) DeepCopy() *RouteTableList {
 	if in == nil {
 		return nil
 	}
-	out := new(UpstreamGroupList)
+	out := new(RouteTableList)
 	in.DeepCopyInto(out)
 	return out
 }
 
 // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *UpstreamGroupList) DeepCopyObject() runtime.Object {
+func (in *RouteTableList) DeepCopyObject() runtime.Object {
 	if c := in.DeepCopy(); c != nil {
 		return c
 	}
@@ -118,22 +243,24 @@ func (in *UpstreamGroupList) DeepCopyObject() runtime.Object {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *UpstreamGroupSpec) DeepCopyInto(out *UpstreamGroupSpec) {
+func (in *RouteTableSpec) DeepCopyInto(out *RouteTableSpec) {
 	*out = *in
-	if in.Destinations != nil {
-		in, out := &in.Destinations, &out.Destinations
-		*out = make([]WeightedDestination, len(*in))
-		copy(*out, *in)
+	if in.Routes != nil {
+		in, out := &in.Routes, &out.Routes
+		*out = make([]Route, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
 	}
 	return
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpstreamGroupSpec.
-func (in *UpstreamGroupSpec) DeepCopy() *UpstreamGroupSpec {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTableSpec.
+func (in *RouteTableSpec) DeepCopy() *RouteTableSpec {
 	if in == nil {
 		return nil
 	}
-	out := new(UpstreamGroupSpec)
+	out := new(RouteTableSpec)
 	in.DeepCopyInto(out)
 	return out
 }
diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go
index c8b0a2ae..4ec16141 100644
--- a/pkg/client/clientset/versioned/clientset.go
+++ b/pkg/client/clientset/versioned/clientset.go
@@ -24,7 +24,7 @@ import (
 	appmeshv1beta1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/appmesh/v1beta1"
 	appmeshv1beta2 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/appmesh/v1beta2"
 	flaggerv1beta1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/flagger/v1beta1"
-	gloov1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/gloo/v1"
+	gatewayv1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/gloo/v1"
 	networkingv1alpha3 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/istio/v1alpha3"
 	projectcontourv1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/projectcontour/v1"
 	splitv1alpha1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/smi/v1alpha1"
@@ -40,7 +40,7 @@ type Interface interface {
 	AppmeshV1beta2() appmeshv1beta2.AppmeshV1beta2Interface
 	AppmeshV1beta1() appmeshv1beta1.AppmeshV1beta1Interface
 	FlaggerV1beta1() flaggerv1beta1.FlaggerV1beta1Interface
-	GlooV1() gloov1.GlooV1Interface
+	GatewayV1() gatewayv1.GatewayV1Interface
 	NetworkingV1alpha3() networkingv1alpha3.NetworkingV1alpha3Interface
 	ProjectcontourV1() projectcontourv1.ProjectcontourV1Interface
 	SplitV1alpha1() splitv1alpha1.SplitV1alpha1Interface
@@ -55,7 +55,7 @@ type Clientset struct {
 	appmeshV1beta2     *appmeshv1beta2.AppmeshV1beta2Client
 	appmeshV1beta1     *appmeshv1beta1.AppmeshV1beta1Client
 	flaggerV1beta1     *flaggerv1beta1.FlaggerV1beta1Client
-	glooV1             *gloov1.GlooV1Client
+	gatewayV1          *gatewayv1.GatewayV1Client
 	networkingV1alpha3 *networkingv1alpha3.NetworkingV1alpha3Client
 	projectcontourV1   *projectcontourv1.ProjectcontourV1Client
 	splitV1alpha1      *splitv1alpha1.SplitV1alpha1Client
@@ -78,9 +78,9 @@ func (c *Clientset) FlaggerV1beta1() flaggerv1beta1.FlaggerV1beta1Interface {
 	return c.flaggerV1beta1
 }
 
-// GlooV1 retrieves the GlooV1Client
-func (c *Clientset) GlooV1() gloov1.GlooV1Interface {
-	return c.glooV1
+// GatewayV1 retrieves the GatewayV1Client
+func (c *Clientset) GatewayV1() gatewayv1.GatewayV1Interface {
+	return c.gatewayV1
 }
 
 // NetworkingV1alpha3 retrieves the NetworkingV1alpha3Client
@@ -141,7 +141,7 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
 	if err != nil {
 		return nil, err
 	}
-	cs.glooV1, err = gloov1.NewForConfig(&configShallowCopy)
+	cs.gatewayV1, err = gatewayv1.NewForConfig(&configShallowCopy)
 	if err != nil {
 		return nil, err
 	}
@@ -180,7 +180,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset {
 	cs.appmeshV1beta2 = appmeshv1beta2.NewForConfigOrDie(c)
 	cs.appmeshV1beta1 = appmeshv1beta1.NewForConfigOrDie(c)
 	cs.flaggerV1beta1 = flaggerv1beta1.NewForConfigOrDie(c)
-	cs.glooV1 = gloov1.NewForConfigOrDie(c)
+	cs.gatewayV1 = gatewayv1.NewForConfigOrDie(c)
 	cs.networkingV1alpha3 = networkingv1alpha3.NewForConfigOrDie(c)
 	cs.projectcontourV1 = projectcontourv1.NewForConfigOrDie(c)
 	cs.splitV1alpha1 = splitv1alpha1.NewForConfigOrDie(c)
@@ -197,7 +197,7 @@ func New(c rest.Interface) *Clientset {
 	cs.appmeshV1beta2 = appmeshv1beta2.New(c)
 	cs.appmeshV1beta1 = appmeshv1beta1.New(c)
 	cs.flaggerV1beta1 = flaggerv1beta1.New(c)
-	cs.glooV1 = gloov1.New(c)
+	cs.gatewayV1 = gatewayv1.New(c)
 	cs.networkingV1alpha3 = networkingv1alpha3.New(c)
 	cs.projectcontourV1 = projectcontourv1.New(c)
 	cs.splitV1alpha1 = splitv1alpha1.New(c)
diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go
index bb5e26c7..2edd507e 100644
--- a/pkg/client/clientset/versioned/fake/clientset_generated.go
+++ b/pkg/client/clientset/versioned/fake/clientset_generated.go
@@ -26,8 +26,8 @@ import (
 	fakeappmeshv1beta2 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/appmesh/v1beta2/fake"
 	flaggerv1beta1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/flagger/v1beta1"
 	fakeflaggerv1beta1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/flagger/v1beta1/fake"
-	gloov1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/gloo/v1"
-	fakegloov1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/gloo/v1/fake"
+	gatewayv1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/gloo/v1"
+	fakegatewayv1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/gloo/v1/fake"
 	networkingv1alpha3 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/istio/v1alpha3"
 	fakenetworkingv1alpha3 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/istio/v1alpha3/fake"
 	projectcontourv1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/projectcontour/v1"
@@ -107,9 +107,9 @@ func (c *Clientset) FlaggerV1beta1() flaggerv1beta1.FlaggerV1beta1Interface {
 	return &fakeflaggerv1beta1.FakeFlaggerV1beta1{Fake: &c.Fake}
 }
 
-// GlooV1 retrieves the GlooV1Client
-func (c *Clientset) GlooV1() gloov1.GlooV1Interface {
-	return &fakegloov1.FakeGlooV1{Fake: &c.Fake}
+// GatewayV1 retrieves the GatewayV1Client
+func (c *Clientset) GatewayV1() gatewayv1.GatewayV1Interface {
+	return &fakegatewayv1.FakeGatewayV1{Fake: &c.Fake}
 }
 
 // NetworkingV1alpha3 retrieves the NetworkingV1alpha3Client
diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go
index b67c97ea..594564df 100644
--- a/pkg/client/clientset/versioned/fake/register.go
+++ b/pkg/client/clientset/versioned/fake/register.go
@@ -22,7 +22,7 @@ import (
 	appmeshv1beta1 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta1"
 	appmeshv1beta2 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta2"
 	flaggerv1beta1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
-	gloov1 "github.com/fluxcd/flagger/pkg/apis/gloo/v1"
+	gatewayv1 "github.com/fluxcd/flagger/pkg/apis/gloo/v1"
 	networkingv1alpha3 "github.com/fluxcd/flagger/pkg/apis/istio/v1alpha3"
 	projectcontourv1 "github.com/fluxcd/flagger/pkg/apis/projectcontour/v1"
 	splitv1alpha1 "github.com/fluxcd/flagger/pkg/apis/smi/v1alpha1"
@@ -42,7 +42,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
 	appmeshv1beta2.AddToScheme,
 	appmeshv1beta1.AddToScheme,
 	flaggerv1beta1.AddToScheme,
-	gloov1.AddToScheme,
+	gatewayv1.AddToScheme,
 	networkingv1alpha3.AddToScheme,
 	projectcontourv1.AddToScheme,
 	splitv1alpha1.AddToScheme,
diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go
index 25d23a6d..069b7f0f 100644
--- a/pkg/client/clientset/versioned/scheme/register.go
+++ b/pkg/client/clientset/versioned/scheme/register.go
@@ -22,7 +22,7 @@ import (
 	appmeshv1beta1 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta1"
 	appmeshv1beta2 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta2"
 	flaggerv1beta1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
-	gloov1 "github.com/fluxcd/flagger/pkg/apis/gloo/v1"
+	gatewayv1 "github.com/fluxcd/flagger/pkg/apis/gloo/v1"
 	networkingv1alpha3 "github.com/fluxcd/flagger/pkg/apis/istio/v1alpha3"
 	projectcontourv1 "github.com/fluxcd/flagger/pkg/apis/projectcontour/v1"
 	splitv1alpha1 "github.com/fluxcd/flagger/pkg/apis/smi/v1alpha1"
@@ -42,7 +42,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
 	appmeshv1beta2.AddToScheme,
 	appmeshv1beta1.AddToScheme,
 	flaggerv1beta1.AddToScheme,
-	gloov1.AddToScheme,
+	gatewayv1.AddToScheme,
 	networkingv1alpha3.AddToScheme,
 	projectcontourv1.AddToScheme,
 	splitv1alpha1.AddToScheme,
diff --git a/pkg/client/clientset/versioned/typed/gloo/v1/fake/fake_gloo_client.go b/pkg/client/clientset/versioned/typed/gloo/v1/fake/fake_gloo_client.go
index ad0765f7..f2815d43 100644
--- a/pkg/client/clientset/versioned/typed/gloo/v1/fake/fake_gloo_client.go
+++ b/pkg/client/clientset/versioned/typed/gloo/v1/fake/fake_gloo_client.go
@@ -24,17 +24,17 @@ import (
 	testing "k8s.io/client-go/testing"
 )
 
-type FakeGlooV1 struct {
+type FakeGatewayV1 struct {
 	*testing.Fake
 }
 
-func (c *FakeGlooV1) UpstreamGroups(namespace string) v1.UpstreamGroupInterface {
-	return &FakeUpstreamGroups{c, namespace}
+func (c *FakeGatewayV1) RouteTables(namespace string) v1.RouteTableInterface {
+	return &FakeRouteTables{c, namespace}
 }
 
 // RESTClient returns a RESTClient that is used to communicate
 // with API server by this client implementation.
-func (c *FakeGlooV1) RESTClient() rest.Interface {
+func (c *FakeGatewayV1) RESTClient() rest.Interface {
 	var ret *rest.RESTClient
 	return ret
 }
diff --git a/pkg/client/clientset/versioned/typed/gloo/v1/fake/fake_routetable.go b/pkg/client/clientset/versioned/typed/gloo/v1/fake/fake_routetable.go
new file mode 100644
index 00000000..0d9d6217
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/gloo/v1/fake/fake_routetable.go
@@ -0,0 +1,130 @@
+/*
+Copyright 2020 The Flux authors
+
+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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+	"context"
+
+	gloov1 "github.com/fluxcd/flagger/pkg/apis/gloo/v1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	labels "k8s.io/apimachinery/pkg/labels"
+	schema "k8s.io/apimachinery/pkg/runtime/schema"
+	types "k8s.io/apimachinery/pkg/types"
+	watch "k8s.io/apimachinery/pkg/watch"
+	testing "k8s.io/client-go/testing"
+)
+
+// FakeRouteTables implements RouteTableInterface
+type FakeRouteTables struct {
+	Fake *FakeGatewayV1
+	ns   string
+}
+
+var routetablesResource = schema.GroupVersionResource{Group: "gateway.solo.io", Version: "v1", Resource: "routetables"}
+
+var routetablesKind = schema.GroupVersionKind{Group: "gateway.solo.io", Version: "v1", Kind: "RouteTable"}
+
+// Get takes name of the routeTable, and returns the corresponding routeTable object, and an error if there is any.
+func (c *FakeRouteTables) Get(ctx context.Context, name string, options v1.GetOptions) (result *gloov1.RouteTable, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewGetAction(routetablesResource, c.ns, name), &gloov1.RouteTable{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*gloov1.RouteTable), err
+}
+
+// List takes label and field selectors, and returns the list of RouteTables that match those selectors.
+func (c *FakeRouteTables) List(ctx context.Context, opts v1.ListOptions) (result *gloov1.RouteTableList, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewListAction(routetablesResource, routetablesKind, c.ns, opts), &gloov1.RouteTableList{})
+
+	if obj == nil {
+		return nil, err
+	}
+
+	label, _, _ := testing.ExtractFromListOptions(opts)
+	if label == nil {
+		label = labels.Everything()
+	}
+	list := &gloov1.RouteTableList{ListMeta: obj.(*gloov1.RouteTableList).ListMeta}
+	for _, item := range obj.(*gloov1.RouteTableList).Items {
+		if label.Matches(labels.Set(item.Labels)) {
+			list.Items = append(list.Items, item)
+		}
+	}
+	return list, err
+}
+
+// Watch returns a watch.Interface that watches the requested routeTables.
+func (c *FakeRouteTables) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+	return c.Fake.
+		InvokesWatch(testing.NewWatchAction(routetablesResource, c.ns, opts))
+
+}
+
+// Create takes the representation of a routeTable and creates it.  Returns the server's representation of the routeTable, and an error, if there is any.
+func (c *FakeRouteTables) Create(ctx context.Context, routeTable *gloov1.RouteTable, opts v1.CreateOptions) (result *gloov1.RouteTable, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewCreateAction(routetablesResource, c.ns, routeTable), &gloov1.RouteTable{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*gloov1.RouteTable), err
+}
+
+// Update takes the representation of a routeTable and updates it. Returns the server's representation of the routeTable, and an error, if there is any.
+func (c *FakeRouteTables) Update(ctx context.Context, routeTable *gloov1.RouteTable, opts v1.UpdateOptions) (result *gloov1.RouteTable, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewUpdateAction(routetablesResource, c.ns, routeTable), &gloov1.RouteTable{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*gloov1.RouteTable), err
+}
+
+// Delete takes name of the routeTable and deletes it. Returns an error if one occurs.
+func (c *FakeRouteTables) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+	_, err := c.Fake.
+		Invokes(testing.NewDeleteAction(routetablesResource, c.ns, name), &gloov1.RouteTable{})
+
+	return err
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *FakeRouteTables) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+	action := testing.NewDeleteCollectionAction(routetablesResource, c.ns, listOpts)
+
+	_, err := c.Fake.Invokes(action, &gloov1.RouteTableList{})
+	return err
+}
+
+// Patch applies the patch and returns the patched routeTable.
+func (c *FakeRouteTables) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *gloov1.RouteTable, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewPatchSubresourceAction(routetablesResource, c.ns, name, pt, data, subresources...), &gloov1.RouteTable{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*gloov1.RouteTable), err
+}
diff --git a/pkg/client/clientset/versioned/typed/gloo/v1/fake/fake_upstreamgroup.go b/pkg/client/clientset/versioned/typed/gloo/v1/fake/fake_upstreamgroup.go
deleted file mode 100644
index efc80800..00000000
--- a/pkg/client/clientset/versioned/typed/gloo/v1/fake/fake_upstreamgroup.go
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
-Copyright 2020 The Flux authors
-
-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.
-*/
-
-// Code generated by client-gen. DO NOT EDIT.
-
-package fake
-
-import (
-	"context"
-
-	gloov1 "github.com/fluxcd/flagger/pkg/apis/gloo/v1"
-	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	labels "k8s.io/apimachinery/pkg/labels"
-	schema "k8s.io/apimachinery/pkg/runtime/schema"
-	types "k8s.io/apimachinery/pkg/types"
-	watch "k8s.io/apimachinery/pkg/watch"
-	testing "k8s.io/client-go/testing"
-)
-
-// FakeUpstreamGroups implements UpstreamGroupInterface
-type FakeUpstreamGroups struct {
-	Fake *FakeGlooV1
-	ns   string
-}
-
-var upstreamgroupsResource = schema.GroupVersionResource{Group: "gloo.solo.io", Version: "v1", Resource: "upstreamgroups"}
-
-var upstreamgroupsKind = schema.GroupVersionKind{Group: "gloo.solo.io", Version: "v1", Kind: "UpstreamGroup"}
-
-// Get takes name of the upstreamGroup, and returns the corresponding upstreamGroup object, and an error if there is any.
-func (c *FakeUpstreamGroups) Get(ctx context.Context, name string, options v1.GetOptions) (result *gloov1.UpstreamGroup, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewGetAction(upstreamgroupsResource, c.ns, name), &gloov1.UpstreamGroup{})
-
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*gloov1.UpstreamGroup), err
-}
-
-// List takes label and field selectors, and returns the list of UpstreamGroups that match those selectors.
-func (c *FakeUpstreamGroups) List(ctx context.Context, opts v1.ListOptions) (result *gloov1.UpstreamGroupList, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewListAction(upstreamgroupsResource, upstreamgroupsKind, c.ns, opts), &gloov1.UpstreamGroupList{})
-
-	if obj == nil {
-		return nil, err
-	}
-
-	label, _, _ := testing.ExtractFromListOptions(opts)
-	if label == nil {
-		label = labels.Everything()
-	}
-	list := &gloov1.UpstreamGroupList{ListMeta: obj.(*gloov1.UpstreamGroupList).ListMeta}
-	for _, item := range obj.(*gloov1.UpstreamGroupList).Items {
-		if label.Matches(labels.Set(item.Labels)) {
-			list.Items = append(list.Items, item)
-		}
-	}
-	return list, err
-}
-
-// Watch returns a watch.Interface that watches the requested upstreamGroups.
-func (c *FakeUpstreamGroups) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
-	return c.Fake.
-		InvokesWatch(testing.NewWatchAction(upstreamgroupsResource, c.ns, opts))
-
-}
-
-// Create takes the representation of a upstreamGroup and creates it.  Returns the server's representation of the upstreamGroup, and an error, if there is any.
-func (c *FakeUpstreamGroups) Create(ctx context.Context, upstreamGroup *gloov1.UpstreamGroup, opts v1.CreateOptions) (result *gloov1.UpstreamGroup, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewCreateAction(upstreamgroupsResource, c.ns, upstreamGroup), &gloov1.UpstreamGroup{})
-
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*gloov1.UpstreamGroup), err
-}
-
-// Update takes the representation of a upstreamGroup and updates it. Returns the server's representation of the upstreamGroup, and an error, if there is any.
-func (c *FakeUpstreamGroups) Update(ctx context.Context, upstreamGroup *gloov1.UpstreamGroup, opts v1.UpdateOptions) (result *gloov1.UpstreamGroup, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewUpdateAction(upstreamgroupsResource, c.ns, upstreamGroup), &gloov1.UpstreamGroup{})
-
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*gloov1.UpstreamGroup), err
-}
-
-// Delete takes name of the upstreamGroup and deletes it. Returns an error if one occurs.
-func (c *FakeUpstreamGroups) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
-	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(upstreamgroupsResource, c.ns, name), &gloov1.UpstreamGroup{})
-
-	return err
-}
-
-// DeleteCollection deletes a collection of objects.
-func (c *FakeUpstreamGroups) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
-	action := testing.NewDeleteCollectionAction(upstreamgroupsResource, c.ns, listOpts)
-
-	_, err := c.Fake.Invokes(action, &gloov1.UpstreamGroupList{})
-	return err
-}
-
-// Patch applies the patch and returns the patched upstreamGroup.
-func (c *FakeUpstreamGroups) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *gloov1.UpstreamGroup, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewPatchSubresourceAction(upstreamgroupsResource, c.ns, name, pt, data, subresources...), &gloov1.UpstreamGroup{})
-
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*gloov1.UpstreamGroup), err
-}
diff --git a/pkg/client/clientset/versioned/typed/gloo/v1/generated_expansion.go b/pkg/client/clientset/versioned/typed/gloo/v1/generated_expansion.go
index 6f708157..972af2a9 100644
--- a/pkg/client/clientset/versioned/typed/gloo/v1/generated_expansion.go
+++ b/pkg/client/clientset/versioned/typed/gloo/v1/generated_expansion.go
@@ -18,4 +18,4 @@ limitations under the License.
 
 package v1
 
-type UpstreamGroupExpansion interface{}
+type RouteTableExpansion interface{}
diff --git a/pkg/client/clientset/versioned/typed/gloo/v1/gloo_client.go b/pkg/client/clientset/versioned/typed/gloo/v1/gloo_client.go
index b952e19c..10e1fe2a 100644
--- a/pkg/client/clientset/versioned/typed/gloo/v1/gloo_client.go
+++ b/pkg/client/clientset/versioned/typed/gloo/v1/gloo_client.go
@@ -24,22 +24,22 @@ import (
 	rest "k8s.io/client-go/rest"
 )
 
-type GlooV1Interface interface {
+type GatewayV1Interface interface {
 	RESTClient() rest.Interface
-	UpstreamGroupsGetter
+	RouteTablesGetter
 }
 
-// GlooV1Client is used to interact with features provided by the gloo.solo.io group.
-type GlooV1Client struct {
+// GatewayV1Client is used to interact with features provided by the gateway.solo.io group.
+type GatewayV1Client struct {
 	restClient rest.Interface
 }
 
-func (c *GlooV1Client) UpstreamGroups(namespace string) UpstreamGroupInterface {
-	return newUpstreamGroups(c, namespace)
+func (c *GatewayV1Client) RouteTables(namespace string) RouteTableInterface {
+	return newRouteTables(c, namespace)
 }
 
-// NewForConfig creates a new GlooV1Client for the given config.
-func NewForConfig(c *rest.Config) (*GlooV1Client, error) {
+// NewForConfig creates a new GatewayV1Client for the given config.
+func NewForConfig(c *rest.Config) (*GatewayV1Client, error) {
 	config := *c
 	if err := setConfigDefaults(&config); err != nil {
 		return nil, err
@@ -48,12 +48,12 @@ func NewForConfig(c *rest.Config) (*GlooV1Client, error) {
 	if err != nil {
 		return nil, err
 	}
-	return &GlooV1Client{client}, nil
+	return &GatewayV1Client{client}, nil
 }
 
-// NewForConfigOrDie creates a new GlooV1Client for the given config and
+// NewForConfigOrDie creates a new GatewayV1Client for the given config and
 // panics if there is an error in the config.
-func NewForConfigOrDie(c *rest.Config) *GlooV1Client {
+func NewForConfigOrDie(c *rest.Config) *GatewayV1Client {
 	client, err := NewForConfig(c)
 	if err != nil {
 		panic(err)
@@ -61,9 +61,9 @@ func NewForConfigOrDie(c *rest.Config) *GlooV1Client {
 	return client
 }
 
-// New creates a new GlooV1Client for the given RESTClient.
-func New(c rest.Interface) *GlooV1Client {
-	return &GlooV1Client{c}
+// New creates a new GatewayV1Client for the given RESTClient.
+func New(c rest.Interface) *GatewayV1Client {
+	return &GatewayV1Client{c}
 }
 
 func setConfigDefaults(config *rest.Config) error {
@@ -81,7 +81,7 @@ func setConfigDefaults(config *rest.Config) error {
 
 // RESTClient returns a RESTClient that is used to communicate
 // with API server by this client implementation.
-func (c *GlooV1Client) RESTClient() rest.Interface {
+func (c *GatewayV1Client) RESTClient() rest.Interface {
 	if c == nil {
 		return nil
 	}
diff --git a/pkg/client/clientset/versioned/typed/gloo/v1/upstreamgroup.go b/pkg/client/clientset/versioned/typed/gloo/v1/routetable.go
similarity index 50%
rename from pkg/client/clientset/versioned/typed/gloo/v1/upstreamgroup.go
rename to pkg/client/clientset/versioned/typed/gloo/v1/routetable.go
index 767fe3db..11936cbf 100644
--- a/pkg/client/clientset/versioned/typed/gloo/v1/upstreamgroup.go
+++ b/pkg/client/clientset/versioned/typed/gloo/v1/routetable.go
@@ -30,45 +30,45 @@ import (
 	rest "k8s.io/client-go/rest"
 )
 
-// UpstreamGroupsGetter has a method to return a UpstreamGroupInterface.
+// RouteTablesGetter has a method to return a RouteTableInterface.
 // A group's client should implement this interface.
-type UpstreamGroupsGetter interface {
-	UpstreamGroups(namespace string) UpstreamGroupInterface
+type RouteTablesGetter interface {
+	RouteTables(namespace string) RouteTableInterface
 }
 
-// UpstreamGroupInterface has methods to work with UpstreamGroup resources.
-type UpstreamGroupInterface interface {
-	Create(ctx context.Context, upstreamGroup *v1.UpstreamGroup, opts metav1.CreateOptions) (*v1.UpstreamGroup, error)
-	Update(ctx context.Context, upstreamGroup *v1.UpstreamGroup, opts metav1.UpdateOptions) (*v1.UpstreamGroup, error)
+// RouteTableInterface has methods to work with RouteTable resources.
+type RouteTableInterface interface {
+	Create(ctx context.Context, routeTable *v1.RouteTable, opts metav1.CreateOptions) (*v1.RouteTable, error)
+	Update(ctx context.Context, routeTable *v1.RouteTable, opts metav1.UpdateOptions) (*v1.RouteTable, error)
 	Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error
 	DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
-	Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.UpstreamGroup, error)
-	List(ctx context.Context, opts metav1.ListOptions) (*v1.UpstreamGroupList, error)
+	Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.RouteTable, error)
+	List(ctx context.Context, opts metav1.ListOptions) (*v1.RouteTableList, error)
 	Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
-	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.UpstreamGroup, err error)
-	UpstreamGroupExpansion
+	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.RouteTable, err error)
+	RouteTableExpansion
 }
 
-// upstreamGroups implements UpstreamGroupInterface
-type upstreamGroups struct {
+// routeTables implements RouteTableInterface
+type routeTables struct {
 	client rest.Interface
 	ns     string
 }
 
-// newUpstreamGroups returns a UpstreamGroups
-func newUpstreamGroups(c *GlooV1Client, namespace string) *upstreamGroups {
-	return &upstreamGroups{
+// newRouteTables returns a RouteTables
+func newRouteTables(c *GatewayV1Client, namespace string) *routeTables {
+	return &routeTables{
 		client: c.RESTClient(),
 		ns:     namespace,
 	}
 }
 
-// Get takes name of the upstreamGroup, and returns the corresponding upstreamGroup object, and an error if there is any.
-func (c *upstreamGroups) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.UpstreamGroup, err error) {
-	result = &v1.UpstreamGroup{}
+// Get takes name of the routeTable, and returns the corresponding routeTable object, and an error if there is any.
+func (c *routeTables) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.RouteTable, err error) {
+	result = &v1.RouteTable{}
 	err = c.client.Get().
 		Namespace(c.ns).
-		Resource("upstreamgroups").
+		Resource("routetables").
 		Name(name).
 		VersionedParams(&options, scheme.ParameterCodec).
 		Do(ctx).
@@ -76,16 +76,16 @@ func (c *upstreamGroups) Get(ctx context.Context, name string, options metav1.Ge
 	return
 }
 
-// List takes label and field selectors, and returns the list of UpstreamGroups that match those selectors.
-func (c *upstreamGroups) List(ctx context.Context, opts metav1.ListOptions) (result *v1.UpstreamGroupList, err error) {
+// List takes label and field selectors, and returns the list of RouteTables that match those selectors.
+func (c *routeTables) List(ctx context.Context, opts metav1.ListOptions) (result *v1.RouteTableList, err error) {
 	var timeout time.Duration
 	if opts.TimeoutSeconds != nil {
 		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
 	}
-	result = &v1.UpstreamGroupList{}
+	result = &v1.RouteTableList{}
 	err = c.client.Get().
 		Namespace(c.ns).
-		Resource("upstreamgroups").
+		Resource("routetables").
 		VersionedParams(&opts, scheme.ParameterCodec).
 		Timeout(timeout).
 		Do(ctx).
@@ -93,8 +93,8 @@ func (c *upstreamGroups) List(ctx context.Context, opts metav1.ListOptions) (res
 	return
 }
 
-// Watch returns a watch.Interface that watches the requested upstreamGroups.
-func (c *upstreamGroups) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
+// Watch returns a watch.Interface that watches the requested routeTables.
+func (c *routeTables) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
 	var timeout time.Duration
 	if opts.TimeoutSeconds != nil {
 		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
@@ -102,44 +102,44 @@ func (c *upstreamGroups) Watch(ctx context.Context, opts metav1.ListOptions) (wa
 	opts.Watch = true
 	return c.client.Get().
 		Namespace(c.ns).
-		Resource("upstreamgroups").
+		Resource("routetables").
 		VersionedParams(&opts, scheme.ParameterCodec).
 		Timeout(timeout).
 		Watch(ctx)
 }
 
-// Create takes the representation of a upstreamGroup and creates it.  Returns the server's representation of the upstreamGroup, and an error, if there is any.
-func (c *upstreamGroups) Create(ctx context.Context, upstreamGroup *v1.UpstreamGroup, opts metav1.CreateOptions) (result *v1.UpstreamGroup, err error) {
-	result = &v1.UpstreamGroup{}
+// Create takes the representation of a routeTable and creates it.  Returns the server's representation of the routeTable, and an error, if there is any.
+func (c *routeTables) Create(ctx context.Context, routeTable *v1.RouteTable, opts metav1.CreateOptions) (result *v1.RouteTable, err error) {
+	result = &v1.RouteTable{}
 	err = c.client.Post().
 		Namespace(c.ns).
-		Resource("upstreamgroups").
+		Resource("routetables").
 		VersionedParams(&opts, scheme.ParameterCodec).
-		Body(upstreamGroup).
+		Body(routeTable).
 		Do(ctx).
 		Into(result)
 	return
 }
 
-// Update takes the representation of a upstreamGroup and updates it. Returns the server's representation of the upstreamGroup, and an error, if there is any.
-func (c *upstreamGroups) Update(ctx context.Context, upstreamGroup *v1.UpstreamGroup, opts metav1.UpdateOptions) (result *v1.UpstreamGroup, err error) {
-	result = &v1.UpstreamGroup{}
+// Update takes the representation of a routeTable and updates it. Returns the server's representation of the routeTable, and an error, if there is any.
+func (c *routeTables) Update(ctx context.Context, routeTable *v1.RouteTable, opts metav1.UpdateOptions) (result *v1.RouteTable, err error) {
+	result = &v1.RouteTable{}
 	err = c.client.Put().
 		Namespace(c.ns).
-		Resource("upstreamgroups").
-		Name(upstreamGroup.Name).
+		Resource("routetables").
+		Name(routeTable.Name).
 		VersionedParams(&opts, scheme.ParameterCodec).
-		Body(upstreamGroup).
+		Body(routeTable).
 		Do(ctx).
 		Into(result)
 	return
 }
 
-// Delete takes name of the upstreamGroup and deletes it. Returns an error if one occurs.
-func (c *upstreamGroups) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
+// Delete takes name of the routeTable and deletes it. Returns an error if one occurs.
+func (c *routeTables) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
 	return c.client.Delete().
 		Namespace(c.ns).
-		Resource("upstreamgroups").
+		Resource("routetables").
 		Name(name).
 		Body(&opts).
 		Do(ctx).
@@ -147,14 +147,14 @@ func (c *upstreamGroups) Delete(ctx context.Context, name string, opts metav1.De
 }
 
 // DeleteCollection deletes a collection of objects.
-func (c *upstreamGroups) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
+func (c *routeTables) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
 	var timeout time.Duration
 	if listOpts.TimeoutSeconds != nil {
 		timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
 	}
 	return c.client.Delete().
 		Namespace(c.ns).
-		Resource("upstreamgroups").
+		Resource("routetables").
 		VersionedParams(&listOpts, scheme.ParameterCodec).
 		Timeout(timeout).
 		Body(&opts).
@@ -162,12 +162,12 @@ func (c *upstreamGroups) DeleteCollection(ctx context.Context, opts metav1.Delet
 		Error()
 }
 
-// Patch applies the patch and returns the patched upstreamGroup.
-func (c *upstreamGroups) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.UpstreamGroup, err error) {
-	result = &v1.UpstreamGroup{}
+// Patch applies the patch and returns the patched routeTable.
+func (c *routeTables) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.RouteTable, err error) {
+	result = &v1.RouteTable{}
 	err = c.client.Patch(pt).
 		Namespace(c.ns).
-		Resource("upstreamgroups").
+		Resource("routetables").
 		Name(name).
 		SubResource(subresources...).
 		VersionedParams(&opts, scheme.ParameterCodec).
diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go
index cd9df556..c744ac0e 100644
--- a/pkg/client/informers/externalversions/factory.go
+++ b/pkg/client/informers/externalversions/factory.go
@@ -180,7 +180,7 @@ type SharedInformerFactory interface {
 
 	Appmesh() appmesh.Interface
 	Flagger() flagger.Interface
-	Gloo() gloo.Interface
+	Gateway() gloo.Interface
 	Networking() istio.Interface
 	Projectcontour() projectcontour.Interface
 	Split() smi.Interface
@@ -195,7 +195,7 @@ func (f *sharedInformerFactory) Flagger() flagger.Interface {
 	return flagger.New(f, f.namespace, f.tweakListOptions)
 }
 
-func (f *sharedInformerFactory) Gloo() gloo.Interface {
+func (f *sharedInformerFactory) Gateway() gloo.Interface {
 	return gloo.New(f, f.namespace, f.tweakListOptions)
 }
 
diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go
index efc6c50e..5182a4e1 100644
--- a/pkg/client/informers/externalversions/generic.go
+++ b/pkg/client/informers/externalversions/generic.go
@@ -84,9 +84,9 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
 	case flaggerv1beta1.SchemeGroupVersion.WithResource("metrictemplates"):
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Flagger().V1beta1().MetricTemplates().Informer()}, nil
 
-		// Group=gloo.solo.io, Version=v1
-	case v1.SchemeGroupVersion.WithResource("upstreamgroups"):
-		return &genericInformer{resource: resource.GroupResource(), informer: f.Gloo().V1().UpstreamGroups().Informer()}, nil
+		// Group=gateway.solo.io, Version=v1
+	case v1.SchemeGroupVersion.WithResource("routetables"):
+		return &genericInformer{resource: resource.GroupResource(), informer: f.Gateway().V1().RouteTables().Informer()}, nil
 
 		// Group=networking.istio.io, Version=v1alpha3
 	case v1alpha3.SchemeGroupVersion.WithResource("destinationrules"):
diff --git a/pkg/client/informers/externalversions/gloo/v1/interface.go b/pkg/client/informers/externalversions/gloo/v1/interface.go
index 84676e08..1b5a8391 100644
--- a/pkg/client/informers/externalversions/gloo/v1/interface.go
+++ b/pkg/client/informers/externalversions/gloo/v1/interface.go
@@ -24,8 +24,8 @@ import (
 
 // Interface provides access to all the informers in this group version.
 type Interface interface {
-	// UpstreamGroups returns a UpstreamGroupInformer.
-	UpstreamGroups() UpstreamGroupInformer
+	// RouteTables returns a RouteTableInformer.
+	RouteTables() RouteTableInformer
 }
 
 type version struct {
@@ -39,7 +39,7 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
 	return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
 }
 
-// UpstreamGroups returns a UpstreamGroupInformer.
-func (v *version) UpstreamGroups() UpstreamGroupInformer {
-	return &upstreamGroupInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
+// RouteTables returns a RouteTableInformer.
+func (v *version) RouteTables() RouteTableInformer {
+	return &routeTableInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
 }
diff --git a/pkg/client/informers/externalversions/gloo/v1/upstreamgroup.go b/pkg/client/informers/externalversions/gloo/v1/routetable.go
similarity index 57%
rename from pkg/client/informers/externalversions/gloo/v1/upstreamgroup.go
rename to pkg/client/informers/externalversions/gloo/v1/routetable.go
index 74faa191..38cafeaa 100644
--- a/pkg/client/informers/externalversions/gloo/v1/upstreamgroup.go
+++ b/pkg/client/informers/externalversions/gloo/v1/routetable.go
@@ -32,59 +32,59 @@ import (
 	cache "k8s.io/client-go/tools/cache"
 )
 
-// UpstreamGroupInformer provides access to a shared informer and lister for
-// UpstreamGroups.
-type UpstreamGroupInformer interface {
+// RouteTableInformer provides access to a shared informer and lister for
+// RouteTables.
+type RouteTableInformer interface {
 	Informer() cache.SharedIndexInformer
-	Lister() v1.UpstreamGroupLister
+	Lister() v1.RouteTableLister
 }
 
-type upstreamGroupInformer struct {
+type routeTableInformer struct {
 	factory          internalinterfaces.SharedInformerFactory
 	tweakListOptions internalinterfaces.TweakListOptionsFunc
 	namespace        string
 }
 
-// NewUpstreamGroupInformer constructs a new informer for UpstreamGroup type.
+// NewRouteTableInformer constructs a new informer for RouteTable type.
 // Always prefer using an informer factory to get a shared informer instead of getting an independent
 // one. This reduces memory footprint and number of connections to the server.
-func NewUpstreamGroupInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
-	return NewFilteredUpstreamGroupInformer(client, namespace, resyncPeriod, indexers, nil)
+func NewRouteTableInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+	return NewFilteredRouteTableInformer(client, namespace, resyncPeriod, indexers, nil)
 }
 
-// NewFilteredUpstreamGroupInformer constructs a new informer for UpstreamGroup type.
+// NewFilteredRouteTableInformer constructs a new informer for RouteTable type.
 // Always prefer using an informer factory to get a shared informer instead of getting an independent
 // one. This reduces memory footprint and number of connections to the server.
-func NewFilteredUpstreamGroupInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
+func NewFilteredRouteTableInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
 	return cache.NewSharedIndexInformer(
 		&cache.ListWatch{
 			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
 				if tweakListOptions != nil {
 					tweakListOptions(&options)
 				}
-				return client.GlooV1().UpstreamGroups(namespace).List(context.TODO(), options)
+				return client.GatewayV1().RouteTables(namespace).List(context.TODO(), options)
 			},
 			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
 				if tweakListOptions != nil {
 					tweakListOptions(&options)
 				}
-				return client.GlooV1().UpstreamGroups(namespace).Watch(context.TODO(), options)
+				return client.GatewayV1().RouteTables(namespace).Watch(context.TODO(), options)
 			},
 		},
-		&gloov1.UpstreamGroup{},
+		&gloov1.RouteTable{},
 		resyncPeriod,
 		indexers,
 	)
 }
 
-func (f *upstreamGroupInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
-	return NewFilteredUpstreamGroupInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+func (f *routeTableInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+	return NewFilteredRouteTableInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
 }
 
-func (f *upstreamGroupInformer) Informer() cache.SharedIndexInformer {
-	return f.factory.InformerFor(&gloov1.UpstreamGroup{}, f.defaultInformer)
+func (f *routeTableInformer) Informer() cache.SharedIndexInformer {
+	return f.factory.InformerFor(&gloov1.RouteTable{}, f.defaultInformer)
 }
 
-func (f *upstreamGroupInformer) Lister() v1.UpstreamGroupLister {
-	return v1.NewUpstreamGroupLister(f.Informer().GetIndexer())
+func (f *routeTableInformer) Lister() v1.RouteTableLister {
+	return v1.NewRouteTableLister(f.Informer().GetIndexer())
 }
diff --git a/pkg/client/listers/gloo/v1/expansion_generated.go b/pkg/client/listers/gloo/v1/expansion_generated.go
index 3e8810cc..4902c03f 100644
--- a/pkg/client/listers/gloo/v1/expansion_generated.go
+++ b/pkg/client/listers/gloo/v1/expansion_generated.go
@@ -18,10 +18,10 @@ limitations under the License.
 
 package v1
 
-// UpstreamGroupListerExpansion allows custom methods to be added to
-// UpstreamGroupLister.
-type UpstreamGroupListerExpansion interface{}
+// RouteTableListerExpansion allows custom methods to be added to
+// RouteTableLister.
+type RouteTableListerExpansion interface{}
 
-// UpstreamGroupNamespaceListerExpansion allows custom methods to be added to
-// UpstreamGroupNamespaceLister.
-type UpstreamGroupNamespaceListerExpansion interface{}
+// RouteTableNamespaceListerExpansion allows custom methods to be added to
+// RouteTableNamespaceLister.
+type RouteTableNamespaceListerExpansion interface{}
diff --git a/pkg/client/listers/gloo/v1/routetable.go b/pkg/client/listers/gloo/v1/routetable.go
new file mode 100644
index 00000000..6e2fb30b
--- /dev/null
+++ b/pkg/client/listers/gloo/v1/routetable.go
@@ -0,0 +1,94 @@
+/*
+Copyright 2020 The Flux authors
+
+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.
+*/
+
+// Code generated by lister-gen. DO NOT EDIT.
+
+package v1
+
+import (
+	v1 "github.com/fluxcd/flagger/pkg/apis/gloo/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/client-go/tools/cache"
+)
+
+// RouteTableLister helps list RouteTables.
+type RouteTableLister interface {
+	// List lists all RouteTables in the indexer.
+	List(selector labels.Selector) (ret []*v1.RouteTable, err error)
+	// RouteTables returns an object that can list and get RouteTables.
+	RouteTables(namespace string) RouteTableNamespaceLister
+	RouteTableListerExpansion
+}
+
+// routeTableLister implements the RouteTableLister interface.
+type routeTableLister struct {
+	indexer cache.Indexer
+}
+
+// NewRouteTableLister returns a new RouteTableLister.
+func NewRouteTableLister(indexer cache.Indexer) RouteTableLister {
+	return &routeTableLister{indexer: indexer}
+}
+
+// List lists all RouteTables in the indexer.
+func (s *routeTableLister) List(selector labels.Selector) (ret []*v1.RouteTable, err error) {
+	err = cache.ListAll(s.indexer, selector, func(m interface{}) {
+		ret = append(ret, m.(*v1.RouteTable))
+	})
+	return ret, err
+}
+
+// RouteTables returns an object that can list and get RouteTables.
+func (s *routeTableLister) RouteTables(namespace string) RouteTableNamespaceLister {
+	return routeTableNamespaceLister{indexer: s.indexer, namespace: namespace}
+}
+
+// RouteTableNamespaceLister helps list and get RouteTables.
+type RouteTableNamespaceLister interface {
+	// List lists all RouteTables in the indexer for a given namespace.
+	List(selector labels.Selector) (ret []*v1.RouteTable, err error)
+	// Get retrieves the RouteTable from the indexer for a given namespace and name.
+	Get(name string) (*v1.RouteTable, error)
+	RouteTableNamespaceListerExpansion
+}
+
+// routeTableNamespaceLister implements the RouteTableNamespaceLister
+// interface.
+type routeTableNamespaceLister struct {
+	indexer   cache.Indexer
+	namespace string
+}
+
+// List lists all RouteTables in the indexer for a given namespace.
+func (s routeTableNamespaceLister) List(selector labels.Selector) (ret []*v1.RouteTable, err error) {
+	err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
+		ret = append(ret, m.(*v1.RouteTable))
+	})
+	return ret, err
+}
+
+// Get retrieves the RouteTable from the indexer for a given namespace and name.
+func (s routeTableNamespaceLister) Get(name string) (*v1.RouteTable, error) {
+	obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
+	if err != nil {
+		return nil, err
+	}
+	if !exists {
+		return nil, errors.NewNotFound(v1.Resource("routetable"), name)
+	}
+	return obj.(*v1.RouteTable), nil
+}
diff --git a/pkg/client/listers/gloo/v1/upstreamgroup.go b/pkg/client/listers/gloo/v1/upstreamgroup.go
deleted file mode 100644
index 430a31e5..00000000
--- a/pkg/client/listers/gloo/v1/upstreamgroup.go
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
-Copyright 2020 The Flux authors
-
-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.
-*/
-
-// Code generated by lister-gen. DO NOT EDIT.
-
-package v1
-
-import (
-	v1 "github.com/fluxcd/flagger/pkg/apis/gloo/v1"
-	"k8s.io/apimachinery/pkg/api/errors"
-	"k8s.io/apimachinery/pkg/labels"
-	"k8s.io/client-go/tools/cache"
-)
-
-// UpstreamGroupLister helps list UpstreamGroups.
-type UpstreamGroupLister interface {
-	// List lists all UpstreamGroups in the indexer.
-	List(selector labels.Selector) (ret []*v1.UpstreamGroup, err error)
-	// UpstreamGroups returns an object that can list and get UpstreamGroups.
-	UpstreamGroups(namespace string) UpstreamGroupNamespaceLister
-	UpstreamGroupListerExpansion
-}
-
-// upstreamGroupLister implements the UpstreamGroupLister interface.
-type upstreamGroupLister struct {
-	indexer cache.Indexer
-}
-
-// NewUpstreamGroupLister returns a new UpstreamGroupLister.
-func NewUpstreamGroupLister(indexer cache.Indexer) UpstreamGroupLister {
-	return &upstreamGroupLister{indexer: indexer}
-}
-
-// List lists all UpstreamGroups in the indexer.
-func (s *upstreamGroupLister) List(selector labels.Selector) (ret []*v1.UpstreamGroup, err error) {
-	err = cache.ListAll(s.indexer, selector, func(m interface{}) {
-		ret = append(ret, m.(*v1.UpstreamGroup))
-	})
-	return ret, err
-}
-
-// UpstreamGroups returns an object that can list and get UpstreamGroups.
-func (s *upstreamGroupLister) UpstreamGroups(namespace string) UpstreamGroupNamespaceLister {
-	return upstreamGroupNamespaceLister{indexer: s.indexer, namespace: namespace}
-}
-
-// UpstreamGroupNamespaceLister helps list and get UpstreamGroups.
-type UpstreamGroupNamespaceLister interface {
-	// List lists all UpstreamGroups in the indexer for a given namespace.
-	List(selector labels.Selector) (ret []*v1.UpstreamGroup, err error)
-	// Get retrieves the UpstreamGroup from the indexer for a given namespace and name.
-	Get(name string) (*v1.UpstreamGroup, error)
-	UpstreamGroupNamespaceListerExpansion
-}
-
-// upstreamGroupNamespaceLister implements the UpstreamGroupNamespaceLister
-// interface.
-type upstreamGroupNamespaceLister struct {
-	indexer   cache.Indexer
-	namespace string
-}
-
-// List lists all UpstreamGroups in the indexer for a given namespace.
-func (s upstreamGroupNamespaceLister) List(selector labels.Selector) (ret []*v1.UpstreamGroup, err error) {
-	err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
-		ret = append(ret, m.(*v1.UpstreamGroup))
-	})
-	return ret, err
-}
-
-// Get retrieves the UpstreamGroup from the indexer for a given namespace and name.
-func (s upstreamGroupNamespaceLister) Get(name string) (*v1.UpstreamGroup, error) {
-	obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
-	if err != nil {
-		return nil, err
-	}
-	if !exists {
-		return nil, errors.NewNotFound(v1.Resource("upstreamgroup"), name)
-	}
-	return obj.(*v1.UpstreamGroup), nil
-}
diff --git a/pkg/router/gloo.go b/pkg/router/gloo.go
index 86e97193..3e849be3 100644
--- a/pkg/router/gloo.go
+++ b/pkg/router/gloo.go
@@ -33,7 +33,7 @@ import (
 	clientset "github.com/fluxcd/flagger/pkg/client/clientset/versioned"
 )
 
-// GlooRouter is managing Istio virtual services
+// GlooRouter is managing Gloo route tables
 type GlooRouter struct {
 	kubeClient          kubernetes.Interface
 	glooClient          clientset.Interface
@@ -42,38 +42,49 @@ type GlooRouter struct {
 	upstreamDiscoveryNs string
 }
 
-// Reconcile creates or updates the Istio virtual service
+// Reconcile creates or updates the Gloo Edge route table
 func (gr *GlooRouter) Reconcile(canary *flaggerv1.Canary) error {
 	apexName, _, _ := canary.GetServiceNames()
 	canaryName := fmt.Sprintf("%s-%s-canary-%v", canary.Namespace, apexName, canary.Spec.Service.Port)
 	primaryName := fmt.Sprintf("%s-%s-primary-%v", canary.Namespace, apexName, canary.Spec.Service.Port)
 
-	newSpec := gloov1.UpstreamGroupSpec{
-		Destinations: []gloov1.WeightedDestination{
+	newSpec := gloov1.RouteTableSpec{
+		Routes: []gloov1.Route{
 			{
-				Destination: gloov1.Destination{
-					Upstream: gloov1.ResourceRef{
-						Name:      primaryName,
-						Namespace: gr.upstreamDiscoveryNs,
+				InheritablePathMatchers: true,
+				Matchers:                getMatchers(canary),
+				Action: gloov1.RouteAction{
+					Destination: gloov1.MultiDestination{
+						Destinations: []gloov1.WeightedDestination{
+							{
+								Destination: gloov1.Destination{
+									Upstream: gloov1.ResourceRef{
+										Name:      primaryName,
+										Namespace: gr.upstreamDiscoveryNs,
+									},
+								},
+								Weight: 100,
+							},
+							{
+								Destination: gloov1.Destination{
+									Upstream: gloov1.ResourceRef{
+										Name:      canaryName,
+										Namespace: gr.upstreamDiscoveryNs,
+									},
+								},
+								Weight: 0,
+							},
+						},
 					},
 				},
-				Weight: 100,
-			},
-			{
-				Destination: gloov1.Destination{
-					Upstream: gloov1.ResourceRef{
-						Name:      canaryName,
-						Namespace: gr.upstreamDiscoveryNs,
-					},
-				},
-				Weight: 0,
 			},
 		},
 	}
 
-	upstreamGroup, err := gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Get(context.TODO(), apexName, metav1.GetOptions{})
+	routeTable, err := gr.glooClient.GatewayV1().RouteTables(canary.Namespace).Get(context.TODO(), apexName, metav1.GetOptions{})
 	if errors.IsNotFound(err) {
-		upstreamGroup = &gloov1.UpstreamGroup{
+
+		routeTable = &gloov1.RouteTable{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      apexName,
 				Namespace: canary.Namespace,
@@ -88,33 +99,33 @@ func (gr *GlooRouter) Reconcile(canary *flaggerv1.Canary) error {
 			Spec: newSpec,
 		}
 
-		_, err = gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Create(context.TODO(), upstreamGroup, metav1.CreateOptions{})
+		_, err = gr.glooClient.GatewayV1().RouteTables(canary.Namespace).Create(context.TODO(), routeTable, metav1.CreateOptions{})
 		if err != nil {
-			return fmt.Errorf("UpstreamGroup %s.%s create error: %w", apexName, canary.Namespace, err)
+			return fmt.Errorf("RouteTable %s.%s create error: %w", apexName, canary.Namespace, err)
 		}
 		gr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
-			Infof("UpstreamGroup %s.%s created", upstreamGroup.GetName(), canary.Namespace)
+			Infof("RouteTable %s.%s created", routeTable.GetName(), canary.Namespace)
 		return nil
 	} else if err != nil {
-		return fmt.Errorf("UpstreamGroup %s.%s get query error: %w", apexName, canary.Namespace, err)
+		return fmt.Errorf("RouteTable %s.%s get query error: %w", apexName, canary.Namespace, err)
 	}
 
-	// update upstreamGroup but keep the original destination weights
-	if upstreamGroup != nil {
+	// update routeTable but keep the original destination weights
+	if routeTable != nil {
 		if diff := cmp.Diff(
 			newSpec,
-			upstreamGroup.Spec,
+			routeTable.Spec,
 			cmpopts.IgnoreFields(gloov1.WeightedDestination{}, "Weight"),
 		); diff != "" {
-			clone := upstreamGroup.DeepCopy()
+			clone := routeTable.DeepCopy()
 			clone.Spec = newSpec
 
-			_, err = gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Update(context.TODO(), clone, metav1.UpdateOptions{})
+			_, err = gr.glooClient.GatewayV1().RouteTables(canary.Namespace).Update(context.TODO(), clone, metav1.UpdateOptions{})
 			if err != nil {
-				return fmt.Errorf("UpstreamGroup %s.%s update error: %w", apexName, canary.Namespace, err)
+				return fmt.Errorf("RouteTable %s.%s update error: %w", apexName, canary.Namespace, err)
 			}
 			gr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
-				Infof("UpstreamGroup %s.%s updated", upstreamGroup.GetName(), canary.Namespace)
+				Infof("RouteTable %s.%s updated", routeTable.GetName(), canary.Namespace)
 		}
 	}
 
@@ -131,18 +142,18 @@ func (gr *GlooRouter) GetRoutes(canary *flaggerv1.Canary) (
 	apexName := canary.Spec.TargetRef.Name
 	primaryName := fmt.Sprintf("%s-%s-primary-%v", canary.Namespace, canary.Spec.TargetRef.Name, canary.Spec.Service.Port)
 
-	upstreamGroup, err := gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Get(context.TODO(), apexName, metav1.GetOptions{})
+	routeTable, err := gr.glooClient.GatewayV1().RouteTables(canary.Namespace).Get(context.TODO(), apexName, metav1.GetOptions{})
 	if err != nil {
-		err = fmt.Errorf("UpstreamGroup %s.%s get query error: %w", apexName, canary.Namespace, err)
+		err = fmt.Errorf("RouteTable %s.%s get query error: %w", apexName, canary.Namespace, err)
 		return
 	}
 
-	if len(upstreamGroup.Spec.Destinations) < 2 {
-		err = fmt.Errorf("UpstreamGroup %s.%s destinations not found", apexName, canary.Namespace)
+	if len(routeTable.Spec.Routes[0].Action.Destination.Destinations) < 2 {
+		err = fmt.Errorf("RouteTable %s.%s destinations not found", apexName, canary.Namespace)
 		return
 	}
 
-	for _, dst := range upstreamGroup.Spec.Destinations {
+	for _, dst := range routeTable.Spec.Routes[0].Action.Destination.Destinations {
 		if dst.Destination.Upstream.Name == primaryName {
 			primaryWeight = int(dst.Weight)
 			canaryWeight = 100 - primaryWeight
@@ -168,37 +179,47 @@ func (gr *GlooRouter) SetRoutes(
 		return fmt.Errorf("RoutingRule %s.%s update failed: no valid weights", apexName, canary.Namespace)
 	}
 
-	upstreamGroup, err := gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Get(context.TODO(), apexName, metav1.GetOptions{})
+	routeTable, err := gr.glooClient.GatewayV1().RouteTables(canary.Namespace).Get(context.TODO(), apexName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("UpstreamGroup %s.%s query error: %w", apexName, canary.Namespace, err)
+		return fmt.Errorf("RouteTable %s.%s query error: %w", apexName, canary.Namespace, err)
 	}
 
-	upstreamGroup.Spec = gloov1.UpstreamGroupSpec{
-		Destinations: []gloov1.WeightedDestination{
-			{
-				Destination: gloov1.Destination{
-					Upstream: gloov1.ResourceRef{
-						Name:      primaryName,
-						Namespace: gr.upstreamDiscoveryNs,
-					},
-				},
-				Weight: uint32(primaryWeight),
-			},
+	routeTable.Spec = gloov1.RouteTableSpec{
+		Routes: []gloov1.Route{
 			{
-				Destination: gloov1.Destination{
-					Upstream: gloov1.ResourceRef{
-						Name:      canaryName,
-						Namespace: gr.upstreamDiscoveryNs,
+				InheritablePathMatchers: true,
+				Matchers:                getMatchers(canary),
+				Action: gloov1.RouteAction{
+					Destination: gloov1.MultiDestination{
+						Destinations: []gloov1.WeightedDestination{
+							{
+								Destination: gloov1.Destination{
+									Upstream: gloov1.ResourceRef{
+										Name:      primaryName,
+										Namespace: gr.upstreamDiscoveryNs,
+									},
+								},
+								Weight: uint32(primaryWeight),
+							},
+							{
+								Destination: gloov1.Destination{
+									Upstream: gloov1.ResourceRef{
+										Name:      canaryName,
+										Namespace: gr.upstreamDiscoveryNs,
+									},
+								},
+								Weight: uint32(canaryWeight),
+							},
+						},
 					},
 				},
-				Weight: uint32(canaryWeight),
 			},
 		},
 	}
 
-	_, err = gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Update(context.TODO(), upstreamGroup, metav1.UpdateOptions{})
+	_, err = gr.glooClient.GatewayV1().RouteTables(canary.Namespace).Update(context.TODO(), routeTable, metav1.UpdateOptions{})
 	if err != nil {
-		return fmt.Errorf("UpstreamGroup %s.%s update error: %w", apexName, canary.Namespace, err)
+		return fmt.Errorf("RouteTable %s.%s update error: %w", apexName, canary.Namespace, err)
 	}
 	return nil
 }
@@ -206,3 +227,51 @@ func (gr *GlooRouter) SetRoutes(
 func (gr *GlooRouter) Finalize(_ *flaggerv1.Canary) error {
 	return nil
 }
+
+func getMatchers(canary *flaggerv1.Canary) []gloov1.Matcher {
+
+	headerMatchers := getHeaderMatchers(canary)
+	methods := getMethods(canary)
+
+	if len(headerMatchers) == 0 && len(methods) == 0 {
+		return nil
+	}
+
+	return []gloov1.Matcher{
+		{
+			Headers: headerMatchers,
+			Methods: methods,
+		},
+	}
+}
+
+func getHeaderMatchers(canary *flaggerv1.Canary) []gloov1.HeaderMatcher {
+	var headerMatchers []gloov1.HeaderMatcher
+	for _, match := range canary.GetAnalysis().Match {
+		for s, stringMatch := range match.Headers {
+			h := gloov1.HeaderMatcher{
+				Name:  s,
+				Value: stringMatch.Exact,
+			}
+			if stringMatch.Regex != "" {
+				h = gloov1.HeaderMatcher{
+					Name:  s,
+					Value: stringMatch.Regex,
+					Regex: true,
+				}
+			}
+			headerMatchers = append(headerMatchers, h)
+		}
+	}
+	return headerMatchers
+}
+
+func getMethods(canary *flaggerv1.Canary) []string {
+	var methods []string
+	for _, match := range canary.GetAnalysis().Match {
+		if stringMatch := match.Method; stringMatch != nil {
+			methods = append(methods, stringMatch.Exact)
+		}
+	}
+	return methods
+}


Test patch:
diff --git a/pkg/router/gloo_test.go b/pkg/router/gloo_test.go
index 13f97e41..dcf1a6c4 100644
--- a/pkg/router/gloo_test.go
+++ b/pkg/router/gloo_test.go
@@ -37,16 +37,36 @@ func TestGlooRouter_Sync(t *testing.T) {
 		kubeClient:    mocks.kubeClient,
 	}
 
+	// init
 	err := router.Reconcile(mocks.canary)
 	require.NoError(t, err)
 
 	// test insert
-	ug, err := router.glooClient.GlooV1().UpstreamGroups("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
+	rt, err := router.glooClient.GatewayV1().RouteTables("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
 	require.NoError(t, err)
-	dests := ug.Spec.Destinations
+	dests := rt.Spec.Routes[0].Action.Destination.Destinations
 	assert.Len(t, dests, 2)
 	assert.Equal(t, uint32(100), dests[0].Weight)
 	assert.Equal(t, uint32(0), dests[1].Weight)
+
+	// test headers update
+	cd, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
+	require.NoError(t, err)
+
+	cdClone := cd.DeepCopy()
+	cdClone.Spec.Analysis.Iterations = 5
+	cdClone.Spec.Analysis.Match = newTestABTest().Spec.Analysis.Match
+	canary, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Update(context.TODO(), cdClone, metav1.UpdateOptions{})
+	require.NoError(t, err)
+
+	// apply change
+	err = router.Reconcile(canary)
+	require.NoError(t, err)
+
+	rt, err = router.glooClient.GatewayV1().RouteTables("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
+	require.NoError(t, err)
+	assert.Equal(t, "x-user-type", rt.Spec.Routes[0].Matchers[0].Headers[0].Name)
+	assert.Equal(t, "test", rt.Spec.Routes[0].Matchers[0].Headers[0].Value)
 }
 
 func TestGlooRouter_SetRoutes(t *testing.T) {
@@ -71,7 +91,7 @@ func TestGlooRouter_SetRoutes(t *testing.T) {
 	err = router.SetRoutes(mocks.canary, p, c, m)
 	require.NoError(t, err)
 
-	ug, err := router.glooClient.GlooV1().UpstreamGroups("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
+	rt, err := router.glooClient.GatewayV1().RouteTables("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
 	require.NoError(t, err)
 
 	var pRoute gloov1.WeightedDestination
@@ -79,7 +99,7 @@ func TestGlooRouter_SetRoutes(t *testing.T) {
 	canaryName := fmt.Sprintf("%s-%s-canary-%v", mocks.canary.Namespace, mocks.canary.Spec.TargetRef.Name, mocks.canary.Spec.Service.Port)
 	primaryName := fmt.Sprintf("%s-%s-primary-%v", mocks.canary.Namespace, mocks.canary.Spec.TargetRef.Name, mocks.canary.Spec.Service.Port)
 
-	for _, dest := range ug.Spec.Destinations {
+	for _, dest := range rt.Spec.Routes[0].Action.Destination.Destinations {
 		if dest.Destination.Upstream.Name == primaryName {
 			pRoute = dest
 		}
@@ -90,6 +110,25 @@ func TestGlooRouter_SetRoutes(t *testing.T) {
 
 	assert.Equal(t, uint32(p), pRoute.Weight)
 	assert.Equal(t, uint32(c), cRoute.Weight)
+
+	cd, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
+	require.NoError(t, err)
+
+	// test update to A/B
+	cdClone := cd.DeepCopy()
+	cdClone.Spec.Analysis.Iterations = 5
+	cdClone.Spec.Analysis.Match = newTestABTest().Spec.Analysis.Match
+	canary, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Update(context.TODO(), cdClone, metav1.UpdateOptions{})
+	require.NoError(t, err)
+
+	// test set routes for A/B
+	err = router.SetRoutes(canary, 0, 100, false)
+	require.NoError(t, err)
+
+	rt, err = router.glooClient.GatewayV1().RouteTables("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
+	require.NoError(t, err)
+	assert.Equal(t, "x-user-type", rt.Spec.Routes[0].Matchers[0].Headers[0].Name)
+	assert.Equal(t, "test", rt.Spec.Routes[0].Matchers[0].Headers[0].Value)
 }
 
 func TestGlooRouter_GetRoutes(t *testing.T) {
diff --git a/test/gloo/install.sh b/test/gloo/install.sh
index b68a6079..98cbfe06 100755
--- a/test/gloo/install.sh
+++ b/test/gloo/install.sh
@@ -2,7 +2,7 @@
 
 set -o errexit
 
-GLOO_VER="1.3.28"
+GLOO_VER="1.6.0"
 REPO_ROOT=$(git rev-parse --show-toplevel)
 
 mkdir -p ${REPO_ROOT}/bin
diff --git a/test/gloo/test-canary.sh b/test/gloo/test-canary.sh
index 72ea019a..95932b48 100755
--- a/test/gloo/test-canary.sh
+++ b/test/gloo/test-canary.sh
@@ -19,8 +19,8 @@ spec:
     routes:
       - matchers:
          - prefix: /
-        routeAction:
-          upstreamGroup:
+        delegateAction:
+          ref:
             name: podinfo
             namespace: test
 EOF
@@ -110,4 +110,85 @@ until ${ok}; do
     fi
 done
 
+echo '>>> Waiting for canary finalization'
+retries=50
+count=0
+ok=false
+until ${ok}; do
+    kubectl -n test get canary/podinfo | grep 'Succeeded' && ok=true || ok=false
+    sleep 5
+    count=$(($count + 1))
+    if [[ ${count} -eq ${retries} ]]; then
+        kubectl -n ingress-nginx logs deployment/flagger
+        echo "No more retries left"
+        exit 1
+    fi
+done
+
 echo '✔ Canary promotion test passed'
+
+cat <<EOF | kubectl apply -f -
+apiVersion: flagger.app/v1beta1
+kind: Canary
+metadata:
+  name: podinfo
+  namespace: test
+spec:
+  provider: gloo
+  targetRef:
+    apiVersion: apps/v1
+    kind: Deployment
+    name: podinfo
+  progressDeadlineSeconds: 60
+  service:
+    port: 80
+    targetPort: 9898
+  analysis:
+    interval: 10s
+    threshold: 5
+    iterations: 5
+    match:
+      - headers:
+          x-canary:
+            exact: "insider"
+    metrics:
+    - name: request-success-rate
+      threshold: 99
+      interval: 1m
+    - name: request-duration
+      threshold: 500
+      interval: 1m
+    webhooks:
+      - name: load-test
+        url: http://flagger-loadtester.test/
+        timeout: 5s
+        metadata:
+          type: cmd
+          cmd: "hey -z 1m -q 5 -c 2 -H 'X-Canary: insider' -host app.example.com http://gateway-proxy.gloo-system"
+          logCmdOutput: "true"
+EOF
+
+echo '>>> Triggering A/B testing'
+kubectl -n test set image deployment/podinfo podinfod=stefanprodan/podinfo:3.1.2
+
+echo '>>> Waiting for A/B testing promotion'
+retries=50
+count=0
+ok=false
+until ${ok}; do
+    kubectl -n test describe deployment/podinfo-primary | grep '3.1.2' && ok=true || ok=false
+    sleep 10
+    kubectl -n gloo-system logs deployment/flagger --tail 1
+    count=$(($count + 1))
+    if [[ ${count} -eq ${retries} ]]; then
+        kubectl -n gloo-system logs deployment/flagger
+        echo "No more retries left"
+        exit 1
+    fi
+done
+
+echo '✔ A/B testing promotion test passed'
+
+kubectl -n gloo-system logs deployment/flagger
+
+echo '✔ All tests passed'
\ No newline at end of file