feat: route crd add timeout fields (#609)

diff --git a/pkg/kube/apisix/apis/config/v1/types.go b/pkg/kube/apisix/apis/config/v1/types.go
index a8b9181..3eea11a 100644
--- a/pkg/kube/apisix/apis/config/v1/types.go
+++ b/pkg/kube/apisix/apis/config/v1/types.go
@@ -55,9 +55,10 @@
 
 // Path defines an URI based route rule.
 type Path struct {
-	Path    string   `json:"path,omitempty" yaml:"path,omitempty"`
-	Backend Backend  `json:"backend,omitempty" yaml:"backend,omitempty"`
-	Plugins []Plugin `json:"plugins,omitempty" yaml:"plugins,omitempty"`
+	Path    string           `json:"path,omitempty" yaml:"path,omitempty"`
+	Backend Backend          `json:"backend,omitempty" yaml:"backend,omitempty"`
+	Timeout *UpstreamTimeout `json:"timeout,omitempty" yaml:"timeout,omitempty"`
+	Plugins []Plugin         `json:"plugins,omitempty" yaml:"plugins,omitempty"`
 }
 
 // Backend defines an upstream, it should be an existing Kubernetes Service.
diff --git a/pkg/kube/apisix/apis/config/v1/zz_generated.deepcopy.go b/pkg/kube/apisix/apis/config/v1/zz_generated.deepcopy.go
index c0f4d32..2dfed52 100644
--- a/pkg/kube/apisix/apis/config/v1/zz_generated.deepcopy.go
+++ b/pkg/kube/apisix/apis/config/v1/zz_generated.deepcopy.go
@@ -610,6 +610,11 @@
 func (in *Path) DeepCopyInto(out *Path) {
 	*out = *in
 	out.Backend = in.Backend
+	if in.Timeout != nil {
+		in, out := &in.Timeout, &out.Timeout
+		*out = new(UpstreamTimeout)
+		**out = **in
+	}
 	if in.Plugins != nil {
 		in, out := &in.Plugins, &out.Plugins
 		*out = make([]Plugin, len(*in))
diff --git a/pkg/kube/apisix/apis/config/v2alpha1/types.go b/pkg/kube/apisix/apis/config/v2alpha1/types.go
index 44b24b8..2ef0c7f 100644
--- a/pkg/kube/apisix/apis/config/v2alpha1/types.go
+++ b/pkg/kube/apisix/apis/config/v2alpha1/types.go
@@ -90,6 +90,7 @@
 	// same URI path (for path matching), route with
 	// higher priority will take effect.
 	Priority int                   `json:"priority,omitempty" yaml:"priority,omitempty"`
+	Timeout  *UpstreamTimeout      `json:"timeout,omitempty" yaml:"timeout,omitempty"`
 	Match    *ApisixRouteHTTPMatch `json:"match,omitempty" yaml:"match,omitempty"`
 	// Deprecated: Backend will be removed in the future, use Backends instead.
 	Backend *ApisixRouteHTTPBackend `json:"backend,omitempty" yaml:"backend,omitempty"`
@@ -195,6 +196,13 @@
 // any plugins.
 type ApisixRouteHTTPPluginConfig map[string]interface{}
 
+// UpstreamTimeout is settings for the read, send and connect to the upstream.
+type UpstreamTimeout struct {
+	Connect metav1.Duration `json:"connect,omitempty" yaml:"connect,omitempty"`
+	Send    metav1.Duration `json:"send,omitempty" yaml:"send,omitempty"`
+	Read    metav1.Duration `json:"read,omitempty" yaml:"read,omitempty"`
+}
+
 // ApisixRouteAuthentication is the authentication-related
 // configuration in ApisixRoute.
 type ApisixRouteAuthentication struct {
diff --git a/pkg/kube/apisix/apis/config/v2alpha1/zz_generated.deepcopy.go b/pkg/kube/apisix/apis/config/v2alpha1/zz_generated.deepcopy.go
index 107459b..b6d05ce 100644
--- a/pkg/kube/apisix/apis/config/v2alpha1/zz_generated.deepcopy.go
+++ b/pkg/kube/apisix/apis/config/v2alpha1/zz_generated.deepcopy.go
@@ -435,6 +435,11 @@
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ApisixRouteHTTP) DeepCopyInto(out *ApisixRouteHTTP) {
 	*out = *in
+	if in.Timeout != nil {
+		in, out := &in.Timeout, &out.Timeout
+		*out = new(UpstreamTimeout)
+		**out = **in
+	}
 	if in.Match != nil {
 		in, out := &in.Match, &out.Match
 		*out = new(ApisixRouteHTTPMatch)
@@ -758,3 +763,22 @@
 	in.DeepCopyInto(out)
 	return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *UpstreamTimeout) DeepCopyInto(out *UpstreamTimeout) {
+	*out = *in
+	out.Connect = in.Connect
+	out.Send = in.Send
+	out.Read = in.Read
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpstreamTimeout.
+func (in *UpstreamTimeout) DeepCopy() *UpstreamTimeout {
+	if in == nil {
+		return nil
+	}
+	out := new(UpstreamTimeout)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/pkg/kube/apisix/apis/config/v2beta1/types.go b/pkg/kube/apisix/apis/config/v2beta1/types.go
index 2bdfe7f..49c7bef 100644
--- a/pkg/kube/apisix/apis/config/v2beta1/types.go
+++ b/pkg/kube/apisix/apis/config/v2beta1/types.go
@@ -45,6 +45,13 @@
 	Stream []ApisixRouteStream `json:"stream,omitempty" yaml:"stream,omitempty"`
 }
 
+// UpstreamTimeout is settings for the read, send and connect to the upstream.
+type UpstreamTimeout struct {
+	Connect metav1.Duration `json:"connect,omitempty" yaml:"connect,omitempty"`
+	Send    metav1.Duration `json:"send,omitempty" yaml:"send,omitempty"`
+	Read    metav1.Duration `json:"read,omitempty" yaml:"read,omitempty"`
+}
+
 // ApisixRouteHTTP represents a single route in for HTTP traffic.
 type ApisixRouteHTTP struct {
 	// The rule name, cannot be empty.
@@ -53,6 +60,7 @@
 	// same URI path (for path matching), route with
 	// higher priority will take effect.
 	Priority int                  `json:"priority,omitempty" yaml:"priority,omitempty"`
+	Timeout  *UpstreamTimeout     `json:"timeout,omitempty" yaml:"timeout,omitempty"`
 	Match    ApisixRouteHTTPMatch `json:"match,omitempty" yaml:"match,omitempty"`
 	// Deprecated: Backend will be removed in the future, use Backends instead.
 	Backend v2alpha1.ApisixRouteHTTPBackend `json:"backend,omitempty" yaml:"backend,omitempty"`
diff --git a/pkg/kube/apisix/apis/config/v2beta1/zz_generated.deepcopy.go b/pkg/kube/apisix/apis/config/v2beta1/zz_generated.deepcopy.go
index f064c5e..e835ddf 100644
--- a/pkg/kube/apisix/apis/config/v2beta1/zz_generated.deepcopy.go
+++ b/pkg/kube/apisix/apis/config/v2beta1/zz_generated.deepcopy.go
@@ -89,6 +89,11 @@
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ApisixRouteHTTP) DeepCopyInto(out *ApisixRouteHTTP) {
 	*out = *in
+	if in.Timeout != nil {
+		in, out := &in.Timeout, &out.Timeout
+		*out = new(UpstreamTimeout)
+		**out = **in
+	}
 	in.Match.DeepCopyInto(&out.Match)
 	in.Backend.DeepCopyInto(&out.Backend)
 	if in.Backends != nil {
@@ -329,3 +334,22 @@
 	in.DeepCopyInto(out)
 	return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *UpstreamTimeout) DeepCopyInto(out *UpstreamTimeout) {
+	*out = *in
+	out.Connect = in.Connect
+	out.Send = in.Send
+	out.Read = in.Read
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpstreamTimeout.
+func (in *UpstreamTimeout) DeepCopy() *UpstreamTimeout {
+	if in == nil {
+		return nil
+	}
+	out := new(UpstreamTimeout)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/pkg/kube/translation/apisix_route.go b/pkg/kube/translation/apisix_route.go
index 3cd4c76..9f71727 100644
--- a/pkg/kube/translation/apisix_route.go
+++ b/pkg/kube/translation/apisix_route.go
@@ -304,6 +304,22 @@
 			return err
 		}
 
+		timeout := &apisixv1.UpstreamTimeout{
+			Connect: apisixv1.DefaultUpstreamTimeout,
+			Read:    apisixv1.DefaultUpstreamTimeout,
+			Send:    apisixv1.DefaultUpstreamTimeout,
+		}
+		if part.Timeout != nil {
+			if part.Timeout.Connect.Duration > 0 {
+				timeout.Connect = int(part.Timeout.Connect.Seconds())
+			}
+			if part.Timeout.Read.Duration > 0 {
+				timeout.Read = int(part.Timeout.Read.Seconds())
+			}
+			if part.Timeout.Send.Duration > 0 {
+				timeout.Send = int(part.Timeout.Send.Seconds())
+			}
+		}
 		pluginMap := make(apisixv1.Plugins)
 		// 2.add route plugins
 		for _, plugin := range part.Plugins {
@@ -362,6 +378,7 @@
 		route.UpstreamId = id.GenID(upstreamName)
 		route.EnableWebsocket = part.Websocket
 		route.Plugins = pluginMap
+		route.Timeout = timeout
 
 		if len(backends) > 0 {
 			weight := _defaultWeight
diff --git a/pkg/types/apisix/v1/types.go b/pkg/types/apisix/v1/types.go
index 2858d53..297f915 100644
--- a/pkg/types/apisix/v1/types.go
+++ b/pkg/types/apisix/v1/types.go
@@ -84,17 +84,18 @@
 type Route struct {
 	Metadata `json:",inline" yaml:",inline"`
 
-	Host            string   `json:"host,omitempty" yaml:"host,omitempty"`
-	Hosts           []string `json:"hosts,omitempty" yaml:"hosts,omitempty"`
-	Uri             string   `json:"uri,omitempty" yaml:"uri,omitempty"`
-	Priority        int      `json:"priority,omitempty" yaml:"priority,omitempty"`
-	Vars            Vars     `json:"vars,omitempty" yaml:"vars,omitempty"`
-	Uris            []string `json:"uris,omitempty" yaml:"uris,omitempty"`
-	Methods         []string `json:"methods,omitempty" yaml:"methods,omitempty"`
-	EnableWebsocket bool     `json:"enable_websocket,omitempty" yaml:"enable_websocket,omitempty"`
-	RemoteAddrs     []string `json:"remote_addrs,omitempty" yaml:"remote_addrs,omitempty"`
-	UpstreamId      string   `json:"upstream_id,omitempty" yaml:"upstream_id,omitempty"`
-	Plugins         Plugins  `json:"plugins,omitempty" yaml:"plugins,omitempty"`
+	Host            string           `json:"host,omitempty" yaml:"host,omitempty"`
+	Hosts           []string         `json:"hosts,omitempty" yaml:"hosts,omitempty"`
+	Uri             string           `json:"uri,omitempty" yaml:"uri,omitempty"`
+	Priority        int              `json:"priority,omitempty" yaml:"priority,omitempty"`
+	Timeout         *UpstreamTimeout `json:"timeout,omitempty" yaml:"timeout,omitempty"`
+	Vars            Vars             `json:"vars,omitempty" yaml:"vars,omitempty"`
+	Uris            []string         `json:"uris,omitempty" yaml:"uris,omitempty"`
+	Methods         []string         `json:"methods,omitempty" yaml:"methods,omitempty"`
+	EnableWebsocket bool             `json:"enable_websocket,omitempty" yaml:"enable_websocket,omitempty"`
+	RemoteAddrs     []string         `json:"remote_addrs,omitempty" yaml:"remote_addrs,omitempty"`
+	UpstreamId      string           `json:"upstream_id,omitempty" yaml:"upstream_id,omitempty"`
+	Plugins         Plugins          `json:"plugins,omitempty" yaml:"plugins,omitempty"`
 }
 
 // Vars represents the route match expressions of APISIX.
diff --git a/pkg/types/apisix/v1/zz_generated.deepcopy.go b/pkg/types/apisix/v1/zz_generated.deepcopy.go
index 64fcdba..0ca8e2c 100644
--- a/pkg/types/apisix/v1/zz_generated.deepcopy.go
+++ b/pkg/types/apisix/v1/zz_generated.deepcopy.go
@@ -235,6 +235,11 @@
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.Timeout != nil {
+		in, out := &in.Timeout, &out.Timeout
+		*out = new(UpstreamTimeout)
+		**out = **in
+	}
 	if in.Vars != nil {
 		in, out := &in.Vars, &out.Vars
 		*out = make(Vars, len(*in))
diff --git a/samples/deploy/crd/v1beta1/ApisixRoute.yaml b/samples/deploy/crd/v1beta1/ApisixRoute.yaml
index fafa917..c546fa4 100644
--- a/samples/deploy/crd/v1beta1/ApisixRoute.yaml
+++ b/samples/deploy/crd/v1beta1/ApisixRoute.yaml
@@ -92,6 +92,15 @@
                     minLength: 1
                   priority:
                     type: integer
+                  timeout:
+                    type: object
+                    properties:
+                      connect:
+                        type: string
+                      send:
+                        type: string
+                      read:
+                        type: string
                   match:
                     type: object
                     required: