Merge pull request #14459 from bprashanth/l7_ingress_resource_refactor

Auto commit by PR queue bot
This commit is contained in:
k8s-merge-robot
2015-10-02 10:20:48 -07:00
23 changed files with 1577 additions and 166 deletions

View File

@@ -292,6 +292,7 @@ _kubectl_get()
must_have_one_noun+=("endpoints") must_have_one_noun+=("endpoints")
must_have_one_noun+=("event") must_have_one_noun+=("event")
must_have_one_noun+=("horizontalpodautoscaler") must_have_one_noun+=("horizontalpodautoscaler")
must_have_one_noun+=("ingress")
must_have_one_noun+=("job") must_have_one_noun+=("job")
must_have_one_noun+=("limitrange") must_have_one_noun+=("limitrange")
must_have_one_noun+=("namespace") must_have_one_noun+=("namespace")
@@ -462,6 +463,7 @@ _kubectl_delete()
must_have_one_noun+=("endpoints") must_have_one_noun+=("endpoints")
must_have_one_noun+=("event") must_have_one_noun+=("event")
must_have_one_noun+=("horizontalpodautoscaler") must_have_one_noun+=("horizontalpodautoscaler")
must_have_one_noun+=("ingress")
must_have_one_noun+=("job") must_have_one_noun+=("job")
must_have_one_noun+=("limitrange") must_have_one_noun+=("limitrange")
must_have_one_noun+=("namespace") must_have_one_noun+=("namespace")
@@ -864,6 +866,7 @@ _kubectl_label()
must_have_one_noun+=("endpoints") must_have_one_noun+=("endpoints")
must_have_one_noun+=("event") must_have_one_noun+=("event")
must_have_one_noun+=("horizontalpodautoscaler") must_have_one_noun+=("horizontalpodautoscaler")
must_have_one_noun+=("ingress")
must_have_one_noun+=("job") must_have_one_noun+=("job")
must_have_one_noun+=("limitrange") must_have_one_noun+=("limitrange")
must_have_one_noun+=("namespace") must_have_one_noun+=("namespace")

View File

@@ -966,6 +966,28 @@ func deepCopy_experimental_DeploymentStrategy(in DeploymentStrategy, out *Deploy
return nil return nil
} }
func deepCopy_experimental_HTTPIngressPath(in HTTPIngressPath, out *HTTPIngressPath, c *conversion.Cloner) error {
out.Path = in.Path
if err := deepCopy_experimental_IngressBackend(in.Backend, &out.Backend, c); err != nil {
return err
}
return nil
}
func deepCopy_experimental_HTTPIngressRuleValue(in HTTPIngressRuleValue, out *HTTPIngressRuleValue, c *conversion.Cloner) error {
if in.Paths != nil {
out.Paths = make([]HTTPIngressPath, len(in.Paths))
for i := range in.Paths {
if err := deepCopy_experimental_HTTPIngressPath(in.Paths[i], &out.Paths[i], c); err != nil {
return err
}
}
} else {
out.Paths = nil
}
return nil
}
func deepCopy_experimental_HorizontalPodAutoscaler(in HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, c *conversion.Cloner) error { func deepCopy_experimental_HorizontalPodAutoscaler(in HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, c *conversion.Cloner) error {
if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err return err
@@ -1058,13 +1080,10 @@ func deepCopy_experimental_Ingress(in Ingress, out *Ingress, c *conversion.Clone
} }
func deepCopy_experimental_IngressBackend(in IngressBackend, out *IngressBackend, c *conversion.Cloner) error { func deepCopy_experimental_IngressBackend(in IngressBackend, out *IngressBackend, c *conversion.Cloner) error {
if err := deepCopy_api_LocalObjectReference(in.ServiceRef, &out.ServiceRef, c); err != nil { out.ServiceName = in.ServiceName
return err
}
if err := deepCopy_util_IntOrString(in.ServicePort, &out.ServicePort, c); err != nil { if err := deepCopy_util_IntOrString(in.ServicePort, &out.ServicePort, c); err != nil {
return err return err
} }
out.Protocol = in.Protocol
return nil return nil
} }
@@ -1088,30 +1107,35 @@ func deepCopy_experimental_IngressList(in IngressList, out *IngressList, c *conv
return nil return nil
} }
func deepCopy_experimental_IngressPath(in IngressPath, out *IngressPath, c *conversion.Cloner) error { func deepCopy_experimental_IngressRule(in IngressRule, out *IngressRule, c *conversion.Cloner) error {
out.Path = in.Path out.Host = in.Host
if err := deepCopy_experimental_IngressBackend(in.Backend, &out.Backend, c); err != nil { if err := deepCopy_experimental_IngressRuleValue(in.IngressRuleValue, &out.IngressRuleValue, c); err != nil {
return err return err
} }
return nil return nil
} }
func deepCopy_experimental_IngressRule(in IngressRule, out *IngressRule, c *conversion.Cloner) error { func deepCopy_experimental_IngressRuleValue(in IngressRuleValue, out *IngressRuleValue, c *conversion.Cloner) error {
out.Host = in.Host if in.HTTP != nil {
if in.Paths != nil { out.HTTP = new(HTTPIngressRuleValue)
out.Paths = make([]IngressPath, len(in.Paths)) if err := deepCopy_experimental_HTTPIngressRuleValue(*in.HTTP, out.HTTP, c); err != nil {
for i := range in.Paths { return err
if err := deepCopy_experimental_IngressPath(in.Paths[i], &out.Paths[i], c); err != nil {
return err
}
} }
} else { } else {
out.Paths = nil out.HTTP = nil
} }
return nil return nil
} }
func deepCopy_experimental_IngressSpec(in IngressSpec, out *IngressSpec, c *conversion.Cloner) error { func deepCopy_experimental_IngressSpec(in IngressSpec, out *IngressSpec, c *conversion.Cloner) error {
if in.Backend != nil {
out.Backend = new(IngressBackend)
if err := deepCopy_experimental_IngressBackend(*in.Backend, out.Backend, c); err != nil {
return err
}
} else {
out.Backend = nil
}
if in.Rules != nil { if in.Rules != nil {
out.Rules = make([]IngressRule, len(in.Rules)) out.Rules = make([]IngressRule, len(in.Rules))
for i := range in.Rules { for i := range in.Rules {
@@ -1458,6 +1482,8 @@ func init() {
deepCopy_experimental_DeploymentSpec, deepCopy_experimental_DeploymentSpec,
deepCopy_experimental_DeploymentStatus, deepCopy_experimental_DeploymentStatus,
deepCopy_experimental_DeploymentStrategy, deepCopy_experimental_DeploymentStrategy,
deepCopy_experimental_HTTPIngressPath,
deepCopy_experimental_HTTPIngressRuleValue,
deepCopy_experimental_HorizontalPodAutoscaler, deepCopy_experimental_HorizontalPodAutoscaler,
deepCopy_experimental_HorizontalPodAutoscalerList, deepCopy_experimental_HorizontalPodAutoscalerList,
deepCopy_experimental_HorizontalPodAutoscalerSpec, deepCopy_experimental_HorizontalPodAutoscalerSpec,
@@ -1465,8 +1491,8 @@ func init() {
deepCopy_experimental_Ingress, deepCopy_experimental_Ingress,
deepCopy_experimental_IngressBackend, deepCopy_experimental_IngressBackend,
deepCopy_experimental_IngressList, deepCopy_experimental_IngressList,
deepCopy_experimental_IngressPath,
deepCopy_experimental_IngressRule, deepCopy_experimental_IngressRule,
deepCopy_experimental_IngressRuleValue,
deepCopy_experimental_IngressSpec, deepCopy_experimental_IngressSpec,
deepCopy_experimental_IngressStatus, deepCopy_experimental_IngressStatus,
deepCopy_experimental_Job, deepCopy_experimental_Job,

View File

@@ -464,9 +464,10 @@ type JobCondition struct {
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
} }
// An Ingress is a way to give services externally-reachable urls. Each Ingress is a // Ingress is a collection of rules that allow inbound connections to reach the
// collection of rules that allow inbound connections to reach the endpoints defined by // endpoints defined by a backend. An Ingress can be configured to give services
// a backend. // externally-reachable urls, load balance traffic, terminate SSL, offer name
// based virtual hosting etc.
type Ingress struct { type Ingress struct {
unversioned.TypeMeta `json:",inline"` unversioned.TypeMeta `json:",inline"`
// Standard object's metadata. // Standard object's metadata.
@@ -495,11 +496,13 @@ type IngressList struct {
// IngressSpec describes the Ingress the user wishes to exist. // IngressSpec describes the Ingress the user wishes to exist.
type IngressSpec struct { type IngressSpec struct {
// TODO: Add the ability to specify load-balancer IP just like what Service has already done? // A default backend capable of servicing requests that don't match any
// A list of rules used to configure the Ingress. // IngressRule. It is optional to allow the loadbalancer controller or
// http://<host>:<port>/<path>?<searchpart> -> IngressBackend // defaulting logic to specify a global default.
// Where parts of the url conform to RFC 1738. Backend *IngressBackend `json:"backend,omitempty"`
// A list of host rules used to configure the Ingress.
Rules []IngressRule `json:"rules"` Rules []IngressRule `json:"rules"`
// TODO: Add the ability to specify load-balancer IP through claims
} }
// IngressStatus describe the current state of the Ingress. // IngressStatus describe the current state of the Ingress.
@@ -508,35 +511,71 @@ type IngressStatus struct {
LoadBalancer api.LoadBalancerStatus `json:"loadBalancer,omitempty"` LoadBalancer api.LoadBalancerStatus `json:"loadBalancer,omitempty"`
} }
// IngressRule represents the rules mapping the paths under a specified host to the related backend services. // IngressRule represents the rules mapping the paths under a specified host to
// the related backend services.
type IngressRule struct { type IngressRule struct {
// Host is the fully qualified domain name of a network host, or its IP // Host is the fully qualified domain name of a network host, as defined
// address as a set of four decimal digit groups separated by ".". // by RFC 3986. Note the following deviations from the "host" part of the
// Conforms to RFC 1738. // URI as defined in the RFC:
// 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the
// IP in the Spec of the parent Ingress.
// 2. The `:` delimiter is not respected because ports are not allowed.
// Currently the port of an Ingress is implicitly :80 for http and
// :443 for https.
// Both these may change in the future.
// Incoming requests are matched against the Host before the IngressRuleValue.
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
// IngressRuleValue represents a rule to route requests for this IngressRule.
IngressRuleValue `json:",inline"`
}
// Paths describe a list of load-balancer rules under the specified host. // IngressRuleValue represents a rule to apply against incoming requests. If the
Paths []IngressPath `json:"paths"` // rule is satisfied, the request is routed to the specified backend.
type IngressRuleValue struct {
//TODO:
// 1. Consider renaming this resource and the associated rules so they
// aren't tied to Ingress. They can be used to route intra-cluster traffic.
// 2. Consider adding fields for ingress-type specific global options
// usable by a loadbalancer, like http keep-alive.
// Currently mixing different types of rules in a single Ingress is
// disallowed, so exactly one of the following must be set.
HTTP *HTTPIngressRuleValue `json:"http"`
}
// HTTPIngressRuleValue is a list of http selectors pointing to IngressBackends.
// In the example: http://<host>/<path>?<searchpart> -> IngressBackend where
// where parts of the url correspond to RFC 3986, this resource will be used
// to match against everything after the last '/' and before the first '?'
// or '#'.
type HTTPIngressRuleValue struct {
// A collection of paths that map requests to IngressBackends.
Paths []HTTPIngressPath `json:"paths"`
// TODO: Consider adding fields for ingress-type specific global
// options usable by a loadbalancer, like http keep-alive.
} }
// IngressPath associates a path regex with an IngressBackend. // IngressPath associates a path regex with an IngressBackend.
// Incoming urls matching the Path are forwarded to the Backend. // Incoming urls matching the Path are forwarded to the Backend.
type IngressPath struct { type HTTPIngressPath struct {
// Path is a regex matched against the url of an incoming request. // Path is a extended POSIX regex as defined by IEEE Std 1003.1,
// (i.e this follows the egrep/unix syntax, not the perl syntax)
// matched against the path of an incoming request. Currently it can
// contain characters disallowed from the conventional "path"
// part of a URL as defined by RFC 3986. Paths must begin with
// a '/'.
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
// Define the referenced service endpoint which the traffic will be forwarded to. // Define the referenced service endpoint which the traffic will be
// forwarded to.
Backend IngressBackend `json:"backend"` Backend IngressBackend `json:"backend"`
} }
// IngressBackend describes all endpoints for a given Service, port and protocol. // IngressBackend describes all endpoints for a given Service and port.
type IngressBackend struct { type IngressBackend struct {
// Specifies the referenced service. // Specifies the name of the referenced service.
ServiceRef api.LocalObjectReference `json:"serviceRef"` ServiceName string `json:"serviceName"`
// Specifies the port of the referenced service. // Specifies the port of the referenced service.
ServicePort util.IntOrString `json:"servicePort,omitempty"` ServicePort util.IntOrString `json:"servicePort"`
// Specifies the protocol of the referenced service.
Protocol api.Protocol `json:"protocol,omitempty"`
} }

View File

@@ -2296,6 +2296,42 @@ func autoconvert_experimental_DeploymentStrategy_To_v1alpha1_DeploymentStrategy(
return nil return nil
} }
func autoconvert_experimental_HTTPIngressPath_To_v1alpha1_HTTPIngressPath(in *experimental.HTTPIngressPath, out *HTTPIngressPath, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*experimental.HTTPIngressPath))(in)
}
out.Path = in.Path
if err := convert_experimental_IngressBackend_To_v1alpha1_IngressBackend(&in.Backend, &out.Backend, s); err != nil {
return err
}
return nil
}
func convert_experimental_HTTPIngressPath_To_v1alpha1_HTTPIngressPath(in *experimental.HTTPIngressPath, out *HTTPIngressPath, s conversion.Scope) error {
return autoconvert_experimental_HTTPIngressPath_To_v1alpha1_HTTPIngressPath(in, out, s)
}
func autoconvert_experimental_HTTPIngressRuleValue_To_v1alpha1_HTTPIngressRuleValue(in *experimental.HTTPIngressRuleValue, out *HTTPIngressRuleValue, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*experimental.HTTPIngressRuleValue))(in)
}
if in.Paths != nil {
out.Paths = make([]HTTPIngressPath, len(in.Paths))
for i := range in.Paths {
if err := convert_experimental_HTTPIngressPath_To_v1alpha1_HTTPIngressPath(&in.Paths[i], &out.Paths[i], s); err != nil {
return err
}
}
} else {
out.Paths = nil
}
return nil
}
func convert_experimental_HTTPIngressRuleValue_To_v1alpha1_HTTPIngressRuleValue(in *experimental.HTTPIngressRuleValue, out *HTTPIngressRuleValue, s conversion.Scope) error {
return autoconvert_experimental_HTTPIngressRuleValue_To_v1alpha1_HTTPIngressRuleValue(in, out, s)
}
func autoconvert_experimental_HorizontalPodAutoscaler_To_v1alpha1_HorizontalPodAutoscaler(in *experimental.HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, s conversion.Scope) error { func autoconvert_experimental_HorizontalPodAutoscaler_To_v1alpha1_HorizontalPodAutoscaler(in *experimental.HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*experimental.HorizontalPodAutoscaler))(in) defaulting.(func(*experimental.HorizontalPodAutoscaler))(in)
@@ -2425,13 +2461,10 @@ func autoconvert_experimental_IngressBackend_To_v1alpha1_IngressBackend(in *expe
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*experimental.IngressBackend))(in) defaulting.(func(*experimental.IngressBackend))(in)
} }
if err := convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.ServiceRef, &out.ServiceRef, s); err != nil { out.ServiceName = in.ServiceName
return err
}
if err := s.Convert(&in.ServicePort, &out.ServicePort, 0); err != nil { if err := s.Convert(&in.ServicePort, &out.ServicePort, 0); err != nil {
return err return err
} }
out.Protocol = v1.Protocol(in.Protocol)
return nil return nil
} }
@@ -2466,35 +2499,13 @@ func convert_experimental_IngressList_To_v1alpha1_IngressList(in *experimental.I
return autoconvert_experimental_IngressList_To_v1alpha1_IngressList(in, out, s) return autoconvert_experimental_IngressList_To_v1alpha1_IngressList(in, out, s)
} }
func autoconvert_experimental_IngressPath_To_v1alpha1_IngressPath(in *experimental.IngressPath, out *IngressPath, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*experimental.IngressPath))(in)
}
out.Path = in.Path
if err := convert_experimental_IngressBackend_To_v1alpha1_IngressBackend(&in.Backend, &out.Backend, s); err != nil {
return err
}
return nil
}
func convert_experimental_IngressPath_To_v1alpha1_IngressPath(in *experimental.IngressPath, out *IngressPath, s conversion.Scope) error {
return autoconvert_experimental_IngressPath_To_v1alpha1_IngressPath(in, out, s)
}
func autoconvert_experimental_IngressRule_To_v1alpha1_IngressRule(in *experimental.IngressRule, out *IngressRule, s conversion.Scope) error { func autoconvert_experimental_IngressRule_To_v1alpha1_IngressRule(in *experimental.IngressRule, out *IngressRule, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*experimental.IngressRule))(in) defaulting.(func(*experimental.IngressRule))(in)
} }
out.Host = in.Host out.Host = in.Host
if in.Paths != nil { if err := convert_experimental_IngressRuleValue_To_v1alpha1_IngressRuleValue(&in.IngressRuleValue, &out.IngressRuleValue, s); err != nil {
out.Paths = make([]IngressPath, len(in.Paths)) return err
for i := range in.Paths {
if err := convert_experimental_IngressPath_To_v1alpha1_IngressPath(&in.Paths[i], &out.Paths[i], s); err != nil {
return err
}
}
} else {
out.Paths = nil
} }
return nil return nil
} }
@@ -2503,10 +2514,37 @@ func convert_experimental_IngressRule_To_v1alpha1_IngressRule(in *experimental.I
return autoconvert_experimental_IngressRule_To_v1alpha1_IngressRule(in, out, s) return autoconvert_experimental_IngressRule_To_v1alpha1_IngressRule(in, out, s)
} }
func autoconvert_experimental_IngressRuleValue_To_v1alpha1_IngressRuleValue(in *experimental.IngressRuleValue, out *IngressRuleValue, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*experimental.IngressRuleValue))(in)
}
if in.HTTP != nil {
out.HTTP = new(HTTPIngressRuleValue)
if err := convert_experimental_HTTPIngressRuleValue_To_v1alpha1_HTTPIngressRuleValue(in.HTTP, out.HTTP, s); err != nil {
return err
}
} else {
out.HTTP = nil
}
return nil
}
func convert_experimental_IngressRuleValue_To_v1alpha1_IngressRuleValue(in *experimental.IngressRuleValue, out *IngressRuleValue, s conversion.Scope) error {
return autoconvert_experimental_IngressRuleValue_To_v1alpha1_IngressRuleValue(in, out, s)
}
func autoconvert_experimental_IngressSpec_To_v1alpha1_IngressSpec(in *experimental.IngressSpec, out *IngressSpec, s conversion.Scope) error { func autoconvert_experimental_IngressSpec_To_v1alpha1_IngressSpec(in *experimental.IngressSpec, out *IngressSpec, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*experimental.IngressSpec))(in) defaulting.(func(*experimental.IngressSpec))(in)
} }
if in.Backend != nil {
out.Backend = new(IngressBackend)
if err := convert_experimental_IngressBackend_To_v1alpha1_IngressBackend(in.Backend, out.Backend, s); err != nil {
return err
}
} else {
out.Backend = nil
}
if in.Rules != nil { if in.Rules != nil {
out.Rules = make([]IngressRule, len(in.Rules)) out.Rules = make([]IngressRule, len(in.Rules))
for i := range in.Rules { for i := range in.Rules {
@@ -3097,6 +3135,42 @@ func convert_v1alpha1_DeploymentStatus_To_experimental_DeploymentStatus(in *Depl
return autoconvert_v1alpha1_DeploymentStatus_To_experimental_DeploymentStatus(in, out, s) return autoconvert_v1alpha1_DeploymentStatus_To_experimental_DeploymentStatus(in, out, s)
} }
func autoconvert_v1alpha1_HTTPIngressPath_To_experimental_HTTPIngressPath(in *HTTPIngressPath, out *experimental.HTTPIngressPath, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*HTTPIngressPath))(in)
}
out.Path = in.Path
if err := convert_v1alpha1_IngressBackend_To_experimental_IngressBackend(&in.Backend, &out.Backend, s); err != nil {
return err
}
return nil
}
func convert_v1alpha1_HTTPIngressPath_To_experimental_HTTPIngressPath(in *HTTPIngressPath, out *experimental.HTTPIngressPath, s conversion.Scope) error {
return autoconvert_v1alpha1_HTTPIngressPath_To_experimental_HTTPIngressPath(in, out, s)
}
func autoconvert_v1alpha1_HTTPIngressRuleValue_To_experimental_HTTPIngressRuleValue(in *HTTPIngressRuleValue, out *experimental.HTTPIngressRuleValue, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*HTTPIngressRuleValue))(in)
}
if in.Paths != nil {
out.Paths = make([]experimental.HTTPIngressPath, len(in.Paths))
for i := range in.Paths {
if err := convert_v1alpha1_HTTPIngressPath_To_experimental_HTTPIngressPath(&in.Paths[i], &out.Paths[i], s); err != nil {
return err
}
}
} else {
out.Paths = nil
}
return nil
}
func convert_v1alpha1_HTTPIngressRuleValue_To_experimental_HTTPIngressRuleValue(in *HTTPIngressRuleValue, out *experimental.HTTPIngressRuleValue, s conversion.Scope) error {
return autoconvert_v1alpha1_HTTPIngressRuleValue_To_experimental_HTTPIngressRuleValue(in, out, s)
}
func autoconvert_v1alpha1_HorizontalPodAutoscaler_To_experimental_HorizontalPodAutoscaler(in *HorizontalPodAutoscaler, out *experimental.HorizontalPodAutoscaler, s conversion.Scope) error { func autoconvert_v1alpha1_HorizontalPodAutoscaler_To_experimental_HorizontalPodAutoscaler(in *HorizontalPodAutoscaler, out *experimental.HorizontalPodAutoscaler, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*HorizontalPodAutoscaler))(in) defaulting.(func(*HorizontalPodAutoscaler))(in)
@@ -3226,13 +3300,10 @@ func autoconvert_v1alpha1_IngressBackend_To_experimental_IngressBackend(in *Ingr
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*IngressBackend))(in) defaulting.(func(*IngressBackend))(in)
} }
if err := convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.ServiceRef, &out.ServiceRef, s); err != nil { out.ServiceName = in.ServiceName
return err
}
if err := s.Convert(&in.ServicePort, &out.ServicePort, 0); err != nil { if err := s.Convert(&in.ServicePort, &out.ServicePort, 0); err != nil {
return err return err
} }
out.Protocol = api.Protocol(in.Protocol)
return nil return nil
} }
@@ -3267,35 +3338,13 @@ func convert_v1alpha1_IngressList_To_experimental_IngressList(in *IngressList, o
return autoconvert_v1alpha1_IngressList_To_experimental_IngressList(in, out, s) return autoconvert_v1alpha1_IngressList_To_experimental_IngressList(in, out, s)
} }
func autoconvert_v1alpha1_IngressPath_To_experimental_IngressPath(in *IngressPath, out *experimental.IngressPath, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*IngressPath))(in)
}
out.Path = in.Path
if err := convert_v1alpha1_IngressBackend_To_experimental_IngressBackend(&in.Backend, &out.Backend, s); err != nil {
return err
}
return nil
}
func convert_v1alpha1_IngressPath_To_experimental_IngressPath(in *IngressPath, out *experimental.IngressPath, s conversion.Scope) error {
return autoconvert_v1alpha1_IngressPath_To_experimental_IngressPath(in, out, s)
}
func autoconvert_v1alpha1_IngressRule_To_experimental_IngressRule(in *IngressRule, out *experimental.IngressRule, s conversion.Scope) error { func autoconvert_v1alpha1_IngressRule_To_experimental_IngressRule(in *IngressRule, out *experimental.IngressRule, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*IngressRule))(in) defaulting.(func(*IngressRule))(in)
} }
out.Host = in.Host out.Host = in.Host
if in.Paths != nil { if err := convert_v1alpha1_IngressRuleValue_To_experimental_IngressRuleValue(&in.IngressRuleValue, &out.IngressRuleValue, s); err != nil {
out.Paths = make([]experimental.IngressPath, len(in.Paths)) return err
for i := range in.Paths {
if err := convert_v1alpha1_IngressPath_To_experimental_IngressPath(&in.Paths[i], &out.Paths[i], s); err != nil {
return err
}
}
} else {
out.Paths = nil
} }
return nil return nil
} }
@@ -3304,10 +3353,37 @@ func convert_v1alpha1_IngressRule_To_experimental_IngressRule(in *IngressRule, o
return autoconvert_v1alpha1_IngressRule_To_experimental_IngressRule(in, out, s) return autoconvert_v1alpha1_IngressRule_To_experimental_IngressRule(in, out, s)
} }
func autoconvert_v1alpha1_IngressRuleValue_To_experimental_IngressRuleValue(in *IngressRuleValue, out *experimental.IngressRuleValue, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*IngressRuleValue))(in)
}
if in.HTTP != nil {
out.HTTP = new(experimental.HTTPIngressRuleValue)
if err := convert_v1alpha1_HTTPIngressRuleValue_To_experimental_HTTPIngressRuleValue(in.HTTP, out.HTTP, s); err != nil {
return err
}
} else {
out.HTTP = nil
}
return nil
}
func convert_v1alpha1_IngressRuleValue_To_experimental_IngressRuleValue(in *IngressRuleValue, out *experimental.IngressRuleValue, s conversion.Scope) error {
return autoconvert_v1alpha1_IngressRuleValue_To_experimental_IngressRuleValue(in, out, s)
}
func autoconvert_v1alpha1_IngressSpec_To_experimental_IngressSpec(in *IngressSpec, out *experimental.IngressSpec, s conversion.Scope) error { func autoconvert_v1alpha1_IngressSpec_To_experimental_IngressSpec(in *IngressSpec, out *experimental.IngressSpec, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*IngressSpec))(in) defaulting.(func(*IngressSpec))(in)
} }
if in.Backend != nil {
out.Backend = new(experimental.IngressBackend)
if err := convert_v1alpha1_IngressBackend_To_experimental_IngressBackend(in.Backend, out.Backend, s); err != nil {
return err
}
} else {
out.Backend = nil
}
if in.Rules != nil { if in.Rules != nil {
out.Rules = make([]experimental.IngressRule, len(in.Rules)) out.Rules = make([]experimental.IngressRule, len(in.Rules))
for i := range in.Rules { for i := range in.Rules {
@@ -3751,13 +3827,15 @@ func init() {
autoconvert_experimental_DeploymentStatus_To_v1alpha1_DeploymentStatus, autoconvert_experimental_DeploymentStatus_To_v1alpha1_DeploymentStatus,
autoconvert_experimental_DeploymentStrategy_To_v1alpha1_DeploymentStrategy, autoconvert_experimental_DeploymentStrategy_To_v1alpha1_DeploymentStrategy,
autoconvert_experimental_Deployment_To_v1alpha1_Deployment, autoconvert_experimental_Deployment_To_v1alpha1_Deployment,
autoconvert_experimental_HTTPIngressPath_To_v1alpha1_HTTPIngressPath,
autoconvert_experimental_HTTPIngressRuleValue_To_v1alpha1_HTTPIngressRuleValue,
autoconvert_experimental_HorizontalPodAutoscalerList_To_v1alpha1_HorizontalPodAutoscalerList, autoconvert_experimental_HorizontalPodAutoscalerList_To_v1alpha1_HorizontalPodAutoscalerList,
autoconvert_experimental_HorizontalPodAutoscalerSpec_To_v1alpha1_HorizontalPodAutoscalerSpec, autoconvert_experimental_HorizontalPodAutoscalerSpec_To_v1alpha1_HorizontalPodAutoscalerSpec,
autoconvert_experimental_HorizontalPodAutoscalerStatus_To_v1alpha1_HorizontalPodAutoscalerStatus, autoconvert_experimental_HorizontalPodAutoscalerStatus_To_v1alpha1_HorizontalPodAutoscalerStatus,
autoconvert_experimental_HorizontalPodAutoscaler_To_v1alpha1_HorizontalPodAutoscaler, autoconvert_experimental_HorizontalPodAutoscaler_To_v1alpha1_HorizontalPodAutoscaler,
autoconvert_experimental_IngressBackend_To_v1alpha1_IngressBackend, autoconvert_experimental_IngressBackend_To_v1alpha1_IngressBackend,
autoconvert_experimental_IngressList_To_v1alpha1_IngressList, autoconvert_experimental_IngressList_To_v1alpha1_IngressList,
autoconvert_experimental_IngressPath_To_v1alpha1_IngressPath, autoconvert_experimental_IngressRuleValue_To_v1alpha1_IngressRuleValue,
autoconvert_experimental_IngressRule_To_v1alpha1_IngressRule, autoconvert_experimental_IngressRule_To_v1alpha1_IngressRule,
autoconvert_experimental_IngressSpec_To_v1alpha1_IngressSpec, autoconvert_experimental_IngressSpec_To_v1alpha1_IngressSpec,
autoconvert_experimental_IngressStatus_To_v1alpha1_IngressStatus, autoconvert_experimental_IngressStatus_To_v1alpha1_IngressStatus,
@@ -3827,13 +3905,15 @@ func init() {
autoconvert_v1alpha1_DeploymentSpec_To_experimental_DeploymentSpec, autoconvert_v1alpha1_DeploymentSpec_To_experimental_DeploymentSpec,
autoconvert_v1alpha1_DeploymentStatus_To_experimental_DeploymentStatus, autoconvert_v1alpha1_DeploymentStatus_To_experimental_DeploymentStatus,
autoconvert_v1alpha1_Deployment_To_experimental_Deployment, autoconvert_v1alpha1_Deployment_To_experimental_Deployment,
autoconvert_v1alpha1_HTTPIngressPath_To_experimental_HTTPIngressPath,
autoconvert_v1alpha1_HTTPIngressRuleValue_To_experimental_HTTPIngressRuleValue,
autoconvert_v1alpha1_HorizontalPodAutoscalerList_To_experimental_HorizontalPodAutoscalerList, autoconvert_v1alpha1_HorizontalPodAutoscalerList_To_experimental_HorizontalPodAutoscalerList,
autoconvert_v1alpha1_HorizontalPodAutoscalerSpec_To_experimental_HorizontalPodAutoscalerSpec, autoconvert_v1alpha1_HorizontalPodAutoscalerSpec_To_experimental_HorizontalPodAutoscalerSpec,
autoconvert_v1alpha1_HorizontalPodAutoscalerStatus_To_experimental_HorizontalPodAutoscalerStatus, autoconvert_v1alpha1_HorizontalPodAutoscalerStatus_To_experimental_HorizontalPodAutoscalerStatus,
autoconvert_v1alpha1_HorizontalPodAutoscaler_To_experimental_HorizontalPodAutoscaler, autoconvert_v1alpha1_HorizontalPodAutoscaler_To_experimental_HorizontalPodAutoscaler,
autoconvert_v1alpha1_IngressBackend_To_experimental_IngressBackend, autoconvert_v1alpha1_IngressBackend_To_experimental_IngressBackend,
autoconvert_v1alpha1_IngressList_To_experimental_IngressList, autoconvert_v1alpha1_IngressList_To_experimental_IngressList,
autoconvert_v1alpha1_IngressPath_To_experimental_IngressPath, autoconvert_v1alpha1_IngressRuleValue_To_experimental_IngressRuleValue,
autoconvert_v1alpha1_IngressRule_To_experimental_IngressRule, autoconvert_v1alpha1_IngressRule_To_experimental_IngressRule,
autoconvert_v1alpha1_IngressSpec_To_experimental_IngressSpec, autoconvert_v1alpha1_IngressSpec_To_experimental_IngressSpec,
autoconvert_v1alpha1_IngressStatus_To_experimental_IngressStatus, autoconvert_v1alpha1_IngressStatus_To_experimental_IngressStatus,

View File

@@ -978,6 +978,28 @@ func deepCopy_v1alpha1_DeploymentStrategy(in DeploymentStrategy, out *Deployment
return nil return nil
} }
func deepCopy_v1alpha1_HTTPIngressPath(in HTTPIngressPath, out *HTTPIngressPath, c *conversion.Cloner) error {
out.Path = in.Path
if err := deepCopy_v1alpha1_IngressBackend(in.Backend, &out.Backend, c); err != nil {
return err
}
return nil
}
func deepCopy_v1alpha1_HTTPIngressRuleValue(in HTTPIngressRuleValue, out *HTTPIngressRuleValue, c *conversion.Cloner) error {
if in.Paths != nil {
out.Paths = make([]HTTPIngressPath, len(in.Paths))
for i := range in.Paths {
if err := deepCopy_v1alpha1_HTTPIngressPath(in.Paths[i], &out.Paths[i], c); err != nil {
return err
}
}
} else {
out.Paths = nil
}
return nil
}
func deepCopy_v1alpha1_HorizontalPodAutoscaler(in HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, c *conversion.Cloner) error { func deepCopy_v1alpha1_HorizontalPodAutoscaler(in HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, c *conversion.Cloner) error {
if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err return err
@@ -1070,13 +1092,10 @@ func deepCopy_v1alpha1_Ingress(in Ingress, out *Ingress, c *conversion.Cloner) e
} }
func deepCopy_v1alpha1_IngressBackend(in IngressBackend, out *IngressBackend, c *conversion.Cloner) error { func deepCopy_v1alpha1_IngressBackend(in IngressBackend, out *IngressBackend, c *conversion.Cloner) error {
if err := deepCopy_v1_LocalObjectReference(in.ServiceRef, &out.ServiceRef, c); err != nil { out.ServiceName = in.ServiceName
return err
}
if err := deepCopy_util_IntOrString(in.ServicePort, &out.ServicePort, c); err != nil { if err := deepCopy_util_IntOrString(in.ServicePort, &out.ServicePort, c); err != nil {
return err return err
} }
out.Protocol = in.Protocol
return nil return nil
} }
@@ -1100,30 +1119,35 @@ func deepCopy_v1alpha1_IngressList(in IngressList, out *IngressList, c *conversi
return nil return nil
} }
func deepCopy_v1alpha1_IngressPath(in IngressPath, out *IngressPath, c *conversion.Cloner) error { func deepCopy_v1alpha1_IngressRule(in IngressRule, out *IngressRule, c *conversion.Cloner) error {
out.Path = in.Path out.Host = in.Host
if err := deepCopy_v1alpha1_IngressBackend(in.Backend, &out.Backend, c); err != nil { if err := deepCopy_v1alpha1_IngressRuleValue(in.IngressRuleValue, &out.IngressRuleValue, c); err != nil {
return err return err
} }
return nil return nil
} }
func deepCopy_v1alpha1_IngressRule(in IngressRule, out *IngressRule, c *conversion.Cloner) error { func deepCopy_v1alpha1_IngressRuleValue(in IngressRuleValue, out *IngressRuleValue, c *conversion.Cloner) error {
out.Host = in.Host if in.HTTP != nil {
if in.Paths != nil { out.HTTP = new(HTTPIngressRuleValue)
out.Paths = make([]IngressPath, len(in.Paths)) if err := deepCopy_v1alpha1_HTTPIngressRuleValue(*in.HTTP, out.HTTP, c); err != nil {
for i := range in.Paths { return err
if err := deepCopy_v1alpha1_IngressPath(in.Paths[i], &out.Paths[i], c); err != nil {
return err
}
} }
} else { } else {
out.Paths = nil out.HTTP = nil
} }
return nil return nil
} }
func deepCopy_v1alpha1_IngressSpec(in IngressSpec, out *IngressSpec, c *conversion.Cloner) error { func deepCopy_v1alpha1_IngressSpec(in IngressSpec, out *IngressSpec, c *conversion.Cloner) error {
if in.Backend != nil {
out.Backend = new(IngressBackend)
if err := deepCopy_v1alpha1_IngressBackend(*in.Backend, out.Backend, c); err != nil {
return err
}
} else {
out.Backend = nil
}
if in.Rules != nil { if in.Rules != nil {
out.Rules = make([]IngressRule, len(in.Rules)) out.Rules = make([]IngressRule, len(in.Rules))
for i := range in.Rules { for i := range in.Rules {
@@ -1480,6 +1504,8 @@ func init() {
deepCopy_v1alpha1_DeploymentSpec, deepCopy_v1alpha1_DeploymentSpec,
deepCopy_v1alpha1_DeploymentStatus, deepCopy_v1alpha1_DeploymentStatus,
deepCopy_v1alpha1_DeploymentStrategy, deepCopy_v1alpha1_DeploymentStrategy,
deepCopy_v1alpha1_HTTPIngressPath,
deepCopy_v1alpha1_HTTPIngressRuleValue,
deepCopy_v1alpha1_HorizontalPodAutoscaler, deepCopy_v1alpha1_HorizontalPodAutoscaler,
deepCopy_v1alpha1_HorizontalPodAutoscalerList, deepCopy_v1alpha1_HorizontalPodAutoscalerList,
deepCopy_v1alpha1_HorizontalPodAutoscalerSpec, deepCopy_v1alpha1_HorizontalPodAutoscalerSpec,
@@ -1487,8 +1513,8 @@ func init() {
deepCopy_v1alpha1_Ingress, deepCopy_v1alpha1_Ingress,
deepCopy_v1alpha1_IngressBackend, deepCopy_v1alpha1_IngressBackend,
deepCopy_v1alpha1_IngressList, deepCopy_v1alpha1_IngressList,
deepCopy_v1alpha1_IngressPath,
deepCopy_v1alpha1_IngressRule, deepCopy_v1alpha1_IngressRule,
deepCopy_v1alpha1_IngressRuleValue,
deepCopy_v1alpha1_IngressSpec, deepCopy_v1alpha1_IngressSpec,
deepCopy_v1alpha1_IngressStatus, deepCopy_v1alpha1_IngressStatus,
deepCopy_v1alpha1_Job, deepCopy_v1alpha1_Job,

View File

@@ -473,9 +473,10 @@ type JobCondition struct {
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
} }
// An Ingress is a way to give services externally-reachable urls. Each Ingress is a // Ingress is a collection of rules that allow inbound connections to reach the
// collection of rules that allow inbound connections to reach the endpoints defined by // endpoints defined by a backend. An Ingress can be configured to give services
// a backend. // externally-reachable urls, load balance traffic, terminate SSL, offer name
// based virtual hosting etc.
type Ingress struct { type Ingress struct {
unversioned.TypeMeta `json:",inline"` unversioned.TypeMeta `json:",inline"`
// Standard object's metadata. // Standard object's metadata.
@@ -504,11 +505,13 @@ type IngressList struct {
// IngressSpec describes the Ingress the user wishes to exist. // IngressSpec describes the Ingress the user wishes to exist.
type IngressSpec struct { type IngressSpec struct {
// TODO: Add the ability to specify load-balancer IP just like what Service has already done? // A default backend capable of servicing requests that don't match any
// A list of rules used to configure the Ingress. // IngressRule. It is optional to allow the loadbalancer controller or
// http://<host>:<port>/<path>?<searchpart> -> IngressBackend // defaulting logic to specify a global default.
// Where parts of the url conform to RFC 1738. Backend *IngressBackend `json:"backend,omitempty"`
// A list of host rules used to configure the Ingress.
Rules []IngressRule `json:"rules"` Rules []IngressRule `json:"rules"`
// TODO: Add the ability to specify load-balancer IP through claims
} }
// IngressStatus describe the current state of the Ingress. // IngressStatus describe the current state of the Ingress.
@@ -517,35 +520,64 @@ type IngressStatus struct {
LoadBalancer v1.LoadBalancerStatus `json:"loadBalancer,omitempty"` LoadBalancer v1.LoadBalancerStatus `json:"loadBalancer,omitempty"`
} }
// IngressRule represents the rules mapping the paths under a specified host to the related backend services. // IngressRule represents the rules mapping the paths under a specified host to
// the related backend services.
type IngressRule struct { type IngressRule struct {
// Host is the fully qualified domain name of a network host, or its IP // Host is the fully qualified domain name of a network host, as defined
// address as a set of four decimal digit groups separated by ".". // by RFC 3986. Note the following deviations from the "host" part of the
// Conforms to RFC 1738. // URI as defined in the RFC:
// 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the
// IP in the Spec of the parent Ingress.
// 2. The `:` delimiter is not respected because ports are not allowed.
// Currently the port of an Ingress is implicitly :80 for http and
// :443 for https.
// Both these may change in the future.
// Incoming requests are matched against the Host before the IngressRuleValue.
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
// IngressRuleValue represents a rule to route requests for this IngressRule.
IngressRuleValue `json:",inline"`
}
// Paths describe a list of load-balancer rules under the specified host. // IngressRuleValue represents a rule to apply against incoming requests. If the
Paths []IngressPath `json:"paths"` // rule is satisfied, the request is routed to the specified backend.
type IngressRuleValue struct {
//TODO:
// 1. Consider renaming this resource and the associated rules so they
// aren't tied to Ingress. They can be used to route intra-cluster traffic.
// 2. Consider adding fields for ingress-type specific global options
// usable by a loadbalancer, like http keep-alive.
// Currently mixing different types of rules in a single Ingress is
// disallowed, so exactly one of the following must be set.
HTTP *HTTPIngressRuleValue `json:"http"`
}
// HTTPIngressRuleValue is a list of http selectors pointing to IngressBackends.
// In the example: http://<host>/<path>?<searchpart> -> IngressBackend where
// parts of the url correspond to RFC 3986, this resource will be used to
// to match against everything after the last '/' and before the first '?'
// or '#'.
type HTTPIngressRuleValue struct {
// A collection of paths that map requests to IngressBackends.
Paths []HTTPIngressPath `json:"paths"`
} }
// IngressPath associates a path regex with an IngressBackend. // IngressPath associates a path regex with an IngressBackend.
// Incoming urls matching the Path are forwarded to the Backend. // Incoming urls matching the Path are forwarded to the Backend.
type IngressPath struct { type HTTPIngressPath struct {
// Path is a regex matched against the url of an incoming request. // Path is a regex matched against the url of an incoming request.
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
// Define the referenced service endpoint which the traffic will be forwarded to. // Define the referenced service endpoint which the traffic will be
// forwarded to.
Backend IngressBackend `json:"backend"` Backend IngressBackend `json:"backend"`
} }
// IngressBackend describes all endpoints for a given Service, port and protocol. // IngressBackend describes all endpoints for a given Service and port.
type IngressBackend struct { type IngressBackend struct {
// Specifies the referenced service. // Specifies the name of the referenced service.
ServiceRef v1.LocalObjectReference `json:"serviceRef"` ServiceName string `json:"serviceName"`
// Specifies the port of the referenced service. // Specifies the port of the referenced service.
ServicePort util.IntOrString `json:"servicePort,omitempty"` ServicePort util.IntOrString `json:"servicePort"`
// Specifies the protocol of the referenced service.
Protocol v1.Protocol `json:"protocol,omitempty"`
} }

View File

@@ -133,6 +133,25 @@ func (DeploymentStrategy) SwaggerDoc() map[string]string {
return map_DeploymentStrategy return map_DeploymentStrategy
} }
var map_HTTPIngressPath = map[string]string{
"": "IngressPath associates a path regex with an IngressBackend. Incoming urls matching the Path are forwarded to the Backend.",
"path": "Path is a regex matched against the url of an incoming request.",
"backend": "Define the referenced service endpoint which the traffic will be forwarded to.",
}
func (HTTPIngressPath) SwaggerDoc() map[string]string {
return map_HTTPIngressPath
}
var map_HTTPIngressRuleValue = map[string]string{
"": "HTTPIngressRuleValue is a list of http selectors pointing to IngressBackends. In the example: http://<host>/<path>?<searchpart> -> IngressBackend where parts of the url correspond to RFC 3986, this resource will be used to to match against everything after the last '/' and before the first '?' or '#'.",
"paths": "A collection of paths that map requests to IngressBackends.",
}
func (HTTPIngressRuleValue) SwaggerDoc() map[string]string {
return map_HTTPIngressRuleValue
}
var map_HorizontalPodAutoscaler = map[string]string{ var map_HorizontalPodAutoscaler = map[string]string{
"": "HorizontalPodAutoscaler represents the configuration of a horizontal pod autoscaler.", "": "HorizontalPodAutoscaler represents the configuration of a horizontal pod autoscaler.",
"metadata": "Standard object metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", "metadata": "Standard object metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata",
@@ -179,7 +198,7 @@ func (HorizontalPodAutoscalerStatus) SwaggerDoc() map[string]string {
} }
var map_Ingress = map[string]string{ var map_Ingress = map[string]string{
"": "An Ingress is a way to give services externally-reachable urls. Each Ingress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend.", "": "Ingress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend. An Ingress can be configured to give services externally-reachable urls, load balance traffic, terminate SSL, offer name based virtual hosting etc.",
"metadata": "Standard object's metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", "metadata": "Standard object's metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata",
"spec": "Spec is the desired state of the Ingress. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status", "spec": "Spec is the desired state of the Ingress. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status",
"status": "Status is the current state of the Ingress. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status", "status": "Status is the current state of the Ingress. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status",
@@ -190,10 +209,9 @@ func (Ingress) SwaggerDoc() map[string]string {
} }
var map_IngressBackend = map[string]string{ var map_IngressBackend = map[string]string{
"": "IngressBackend describes all endpoints for a given Service, port and protocol.", "": "IngressBackend describes all endpoints for a given Service and port.",
"serviceRef": "Specifies the referenced service.", "serviceName": "Specifies the name of the referenced service.",
"servicePort": "Specifies the port of the referenced service.", "servicePort": "Specifies the port of the referenced service.",
"protocol": "Specifies the protocol of the referenced service.",
} }
func (IngressBackend) SwaggerDoc() map[string]string { func (IngressBackend) SwaggerDoc() map[string]string {
@@ -210,29 +228,28 @@ func (IngressList) SwaggerDoc() map[string]string {
return map_IngressList return map_IngressList
} }
var map_IngressPath = map[string]string{
"": "IngressPath associates a path regex with an IngressBackend. Incoming urls matching the Path are forwarded to the Backend.",
"path": "Path is a regex matched against the url of an incoming request.",
"backend": "Define the referenced service endpoint which the traffic will be forwarded to.",
}
func (IngressPath) SwaggerDoc() map[string]string {
return map_IngressPath
}
var map_IngressRule = map[string]string{ var map_IngressRule = map[string]string{
"": "IngressRule represents the rules mapping the paths under a specified host to the related backend services.", "": "IngressRule represents the rules mapping the paths under a specified host to the related backend services.",
"host": "Host is the fully qualified domain name of a network host, or its IP address as a set of four decimal digit groups separated by \".\". Conforms to RFC 1738.", "host": "Host is the fully qualified domain name of a network host, as defined by RFC 3986. Note the following deviations from the \"host\" part of the URI as defined in the RFC: 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the\n\t IP in the Spec of the parent Ingress.\n2. The `:` delimiter is not respected because ports are not allowed.\n\t Currently the port of an Ingress is implicitly :80 for http and\n\t :443 for https.\nBoth these may change in the future. Incoming requests are matched against the Host before the IngressRuleValue.",
"paths": "Paths describe a list of load-balancer rules under the specified host.",
} }
func (IngressRule) SwaggerDoc() map[string]string { func (IngressRule) SwaggerDoc() map[string]string {
return map_IngressRule return map_IngressRule
} }
var map_IngressRuleValue = map[string]string{
"": "IngressRuleValue represents a rule to apply against incoming requests. If the rule is satisfied, the request is routed to the specified backend.",
"http": "Currently mixing different types of rules in a single Ingress is disallowed, so exactly one of the following must be set.",
}
func (IngressRuleValue) SwaggerDoc() map[string]string {
return map_IngressRuleValue
}
var map_IngressSpec = map[string]string{ var map_IngressSpec = map[string]string{
"": "IngressSpec describes the Ingress the user wishes to exist.", "": "IngressSpec describes the Ingress the user wishes to exist.",
"rules": "A list of rules used to configure the Ingress. http://<host>:<port>/<path>?<searchpart> -> IngressBackend Where parts of the url conform to RFC 1738.", "backend": "A default backend capable of servicing requests that don't match any IngressRule. It is optional to allow the loadbalancer controller or defaulting logic to specify a global default.",
"rules": "A list of host rules used to configure the Ingress.",
} }
func (IngressSpec) SwaggerDoc() map[string]string { func (IngressSpec) SwaggerDoc() map[string]string {

View File

@@ -17,7 +17,11 @@ limitations under the License.
package validation package validation
import ( import (
"fmt"
"net"
"regexp"
"strconv" "strconv"
"strings"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
apivalidation "k8s.io/kubernetes/pkg/api/validation" apivalidation "k8s.io/kubernetes/pkg/api/validation"
@@ -27,10 +31,19 @@ import (
errs "k8s.io/kubernetes/pkg/util/fielderrors" errs "k8s.io/kubernetes/pkg/util/fielderrors"
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/util/validation" "k8s.io/kubernetes/pkg/util/validation"
utilvalidation "k8s.io/kubernetes/pkg/util/validation"
) )
const isNegativeErrorMsg string = `must be non-negative` const isNegativeErrorMsg string = `must be non-negative`
// TODO: Expose from apivalidation instead of duplicating.
func intervalErrorMsg(lo, hi int) string {
return fmt.Sprintf(`must be greater than %d and less than %d`, lo, hi)
}
var portRangeErrorMsg string = intervalErrorMsg(0, 65536)
var portNameErrorMsg string = fmt.Sprintf(`must be an IANA_SVC_NAME (at most 15 characters, matching regex %s, it must contain at least one letter [a-z], and hyphens cannot be adjacent to other hyphens): e.g. "http"`, validation.IdentifierNoHyphensBeginEndFmt)
// ValidateHorizontalPodAutoscaler can be used to check whether the given autoscaler name is valid. // ValidateHorizontalPodAutoscaler can be used to check whether the given autoscaler name is valid.
// Prefix indicates this name will be used as part of generation, in which case trailing dashes are allowed. // Prefix indicates this name will be used as part of generation, in which case trailing dashes are allowed.
func ValidateHorizontalPodAutoscalerName(name string, prefix bool) (bool, string) { func ValidateHorizontalPodAutoscalerName(name string, prefix bool) (bool, string) {
@@ -371,3 +384,120 @@ func ValidateJobStatusUpdate(oldStatus, status experimental.JobStatus) errs.Vali
allErrs = append(allErrs, ValidateJobStatus(&status)...) allErrs = append(allErrs, ValidateJobStatus(&status)...)
return allErrs return allErrs
} }
// ValidateIngress tests if required fields in the Ingress are set.
func ValidateIngress(ingress *experimental.Ingress) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&ingress.ObjectMeta, true, ValidateIngressName).Prefix("metadata")...)
allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec).Prefix("spec")...)
return allErrs
}
// ValidateIngressName validates that the given name can be used as an Ingress name.
func ValidateIngressName(name string, prefix bool) (bool, string) {
return apivalidation.NameIsDNSSubdomain(name, prefix)
}
// ValidateIngressSpec tests if required fields in the IngressSpec are set.
func ValidateIngressSpec(spec *experimental.IngressSpec) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
// TODO: Is a default backend mandatory?
if spec.Backend != nil {
allErrs = append(allErrs, validateIngressBackend(spec.Backend).Prefix("backend")...)
} else if len(spec.Rules) == 0 {
allErrs = append(allErrs, errs.NewFieldInvalid("rules", spec.Rules, "Either a default backend or a set of host rules are required for ingress."))
}
if len(spec.Rules) > 0 {
allErrs = append(allErrs, validateIngressRules(spec.Rules).Prefix("rules")...)
}
return allErrs
}
// ValidateIngressUpdate tests if required fields in the Ingress are set.
func ValidateIngressUpdate(oldIngress, ingress *experimental.Ingress) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta).Prefix("metadata")...)
allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec).Prefix("spec")...)
return allErrs
}
func validateIngressRules(IngressRules []experimental.IngressRule) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
if len(IngressRules) == 0 {
return append(allErrs, errs.NewFieldRequired("IngressRules"))
}
for _, ih := range IngressRules {
if len(ih.Host) > 0 {
// TODO: Ports and ips are allowed in the host part of a url
// according to RFC 3986, consider allowing them.
if valid, errMsg := apivalidation.NameIsDNSSubdomain(ih.Host, false); !valid {
allErrs = append(allErrs, errs.NewFieldInvalid("host", ih.Host, errMsg))
}
if isIP := (net.ParseIP(ih.Host) != nil); isIP {
allErrs = append(allErrs, errs.NewFieldInvalid("host", ih.Host, "Host must be a DNS name, not ip address"))
}
}
allErrs = append(allErrs, validateIngressRuleValue(&ih.IngressRuleValue).Prefix("ingressRule")...)
}
return allErrs
}
func validateIngressRuleValue(ingressRule *experimental.IngressRuleValue) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
if ingressRule.HTTP != nil {
allErrs = append(allErrs, validateHTTPIngressRuleValue(ingressRule.HTTP).Prefix("http")...)
}
return allErrs
}
func validateHTTPIngressRuleValue(httpIngressRuleValue *experimental.HTTPIngressRuleValue) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
if len(httpIngressRuleValue.Paths) == 0 {
allErrs = append(allErrs, errs.NewFieldRequired("paths"))
}
for _, rule := range httpIngressRuleValue.Paths {
if len(rule.Path) > 0 {
if !strings.HasPrefix(rule.Path, "/") {
allErrs = append(allErrs, errs.NewFieldInvalid("path", rule.Path, "path must begin with /"))
}
// TODO: More draconian path regex validation.
// Path must be a valid regex. This is the basic requirement.
// In addition to this any characters not allowed in a path per
// RFC 3986 section-3.3 cannot appear as a literal in the regex.
// Consider the example: http://host/valid?#bar, everything after
// the last '/' is a valid regex that matches valid#bar, which
// isn't a valid path, because the path terminates at the first ?
// or #. A more sophisticated form of validation would detect that
// the user is confusing url regexes with path regexes.
_, err := regexp.CompilePOSIX(rule.Path)
if err != nil {
allErrs = append(allErrs, errs.NewFieldInvalid("path", rule.Path, "httpIngressRuleValue.path must be a valid regex."))
}
}
allErrs = append(allErrs, validateIngressBackend(&rule.Backend).Prefix("backend")...)
}
return allErrs
}
// validateIngressBackend tests if a given backend is valid.
func validateIngressBackend(backend *experimental.IngressBackend) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
// All backends must reference a single local service by name, and a single service port by name or number.
if len(backend.ServiceName) == 0 {
return append(allErrs, errs.NewFieldRequired("serviceName"))
} else if ok, errMsg := apivalidation.ValidateServiceName(backend.ServiceName, false); !ok {
allErrs = append(allErrs, errs.NewFieldInvalid("serviceName", backend.ServiceName, errMsg))
}
if backend.ServicePort.Kind == util.IntstrString {
if !utilvalidation.IsDNS1123Label(backend.ServicePort.StrVal) {
allErrs = append(allErrs, errs.NewFieldInvalid("servicePort", backend.ServicePort.StrVal, apivalidation.DNS1123LabelErrorMsg))
}
if !utilvalidation.IsValidPortName(backend.ServicePort.StrVal) {
allErrs = append(allErrs, errs.NewFieldInvalid("servicePort", backend.ServicePort.StrVal, portNameErrorMsg))
}
} else if !utilvalidation.IsValidPortNum(backend.ServicePort.IntVal) {
allErrs = append(allErrs, errs.NewFieldInvalid("servicePort", backend.ServicePort, portRangeErrorMsg))
}
return allErrs
}

View File

@@ -17,6 +17,7 @@ limitations under the License.
package validation package validation
import ( import (
"fmt"
"strings" "strings"
"testing" "testing"
@@ -848,3 +849,104 @@ func TestValidateJob(t *testing.T) {
} }
} }
} }
type ingressRules map[string]string
func TestValidateIngress(t *testing.T) {
defaultBackend := experimental.IngressBackend{
ServiceName: "default-backend",
ServicePort: util.IntOrString{Kind: util.IntstrInt, IntVal: 80},
}
newValid := func() experimental.Ingress {
return experimental.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: experimental.IngressSpec{
Backend: &experimental.IngressBackend{
ServiceName: "default-backend",
ServicePort: util.IntOrString{Kind: util.IntstrInt, IntVal: 80},
},
Rules: []experimental.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: experimental.IngressRuleValue{
HTTP: &experimental.HTTPIngressRuleValue{
Paths: []experimental.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
Status: experimental.IngressStatus{
LoadBalancer: api.LoadBalancerStatus{
Ingress: []api.LoadBalancerIngress{
{IP: "127.0.0.1"},
},
},
},
}
}
servicelessBackend := newValid()
servicelessBackend.Spec.Backend.ServiceName = ""
invalidNameBackend := newValid()
invalidNameBackend.Spec.Backend.ServiceName = "defaultBackend"
noPortBackend := newValid()
noPortBackend.Spec.Backend = &experimental.IngressBackend{ServiceName: defaultBackend.ServiceName}
noForwardSlashPath := newValid()
noForwardSlashPath.Spec.Rules[0].IngressRuleValue.HTTP.Paths = []experimental.HTTPIngressPath{
{
Path: "invalid",
Backend: defaultBackend,
},
}
noPaths := newValid()
noPaths.Spec.Rules[0].IngressRuleValue.HTTP.Paths = []experimental.HTTPIngressPath{}
badHost := newValid()
badHost.Spec.Rules[0].Host = "foobar:80"
badRegexPath := newValid()
badPathExpr := "/invalid["
badRegexPath.Spec.Rules[0].IngressRuleValue.HTTP.Paths = []experimental.HTTPIngressPath{
{
Path: badPathExpr,
Backend: defaultBackend,
},
}
badPathErr := fmt.Sprintf("spec.rules.ingressRule.http.path: invalid value '%v'",
badPathExpr)
hostIP := "127.0.0.1"
badHostIP := newValid()
badHostIP.Spec.Rules[0].Host = hostIP
badHostIPErr := fmt.Sprintf("spec.rules.host: invalid value '%v'", hostIP)
errorCases := map[string]experimental.Ingress{
"spec.backend.serviceName: required value": servicelessBackend,
"spec.backend.serviceName: invalid value": invalidNameBackend,
"spec.backend.servicePort: invalid value": noPortBackend,
"spec.rules.host: invalid value": badHost,
"spec.rules.ingressRule.http.paths: required value": noPaths,
"spec.rules.ingressRule.http.path: invalid value": noForwardSlashPath,
}
errorCases[badPathErr] = badRegexPath
errorCases[badHostIPErr] = badHostIP
for k, v := range errorCases {
errs := ValidateIngress(&v)
if len(errs) == 0 {
t.Errorf("expected failure for %s", k)
} else {
s := strings.Split(k, ":")
err := errs[0].(*errors.ValidationError)
if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
t.Errorf("unexpected error: %v, expected: %s", errs[0], k)
}
}
}
}

View File

@@ -37,6 +37,7 @@ type ExperimentalInterface interface {
DaemonSetsNamespacer DaemonSetsNamespacer
DeploymentsNamespacer DeploymentsNamespacer
JobsNamespacer JobsNamespacer
IngressNamespacer
} }
// ExperimentalClient is used to interact with experimental Kubernetes features. // ExperimentalClient is used to interact with experimental Kubernetes features.
@@ -95,6 +96,10 @@ func (c *ExperimentalClient) Jobs(namespace string) JobInterface {
return newJobs(c, namespace) return newJobs(c, namespace)
} }
func (c *ExperimentalClient) Ingress(namespace string) IngressInterface {
return newIngress(c, namespace)
}
// NewExperimental creates a new ExperimentalClient for the given config. This client // NewExperimental creates a new ExperimentalClient for the given config. This client
// provides access to experimental Kubernetes features. // provides access to experimental Kubernetes features.
// Experimental features are not supported and may be changed or removed in // Experimental features are not supported and may be changed or removed in

View File

@@ -0,0 +1,112 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package unversioned
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/experimental"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/watch"
)
// IngressNamespacer has methods to work with Ingress resources in a namespace
type IngressNamespacer interface {
Ingress(namespace string) IngressInterface
}
// IngressInterface exposes methods to work on Ingress resources.
type IngressInterface interface {
List(label labels.Selector, field fields.Selector) (*experimental.IngressList, error)
Get(name string) (*experimental.Ingress, error)
Create(ingress *experimental.Ingress) (*experimental.Ingress, error)
Update(ingress *experimental.Ingress) (*experimental.Ingress, error)
Delete(name string, options *api.DeleteOptions) error
Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error)
UpdateStatus(ingress *experimental.Ingress) (*experimental.Ingress, error)
}
// ingress implements IngressNamespacer interface
type ingress struct {
r *ExperimentalClient
ns string
}
// newIngress returns a ingress
func newIngress(c *ExperimentalClient, namespace string) *ingress {
return &ingress{c, namespace}
}
// List returns a list of ingress that match the label and field selectors.
func (c *ingress) List(label labels.Selector, field fields.Selector) (result *experimental.IngressList, err error) {
result = &experimental.IngressList{}
err = c.r.Get().Namespace(c.ns).Resource("ingress").LabelsSelectorParam(label).FieldsSelectorParam(field).Do().Into(result)
return
}
// Get returns information about a particular ingress.
func (c *ingress) Get(name string) (result *experimental.Ingress, err error) {
result = &experimental.Ingress{}
err = c.r.Get().Namespace(c.ns).Resource("ingress").Name(name).Do().Into(result)
return
}
// Create creates a new ingress.
func (c *ingress) Create(ingress *experimental.Ingress) (result *experimental.Ingress, err error) {
result = &experimental.Ingress{}
err = c.r.Post().Namespace(c.ns).Resource("ingress").Body(ingress).Do().Into(result)
return
}
// Update updates an existing ingress.
func (c *ingress) Update(ingress *experimental.Ingress) (result *experimental.Ingress, err error) {
result = &experimental.Ingress{}
err = c.r.Put().Namespace(c.ns).Resource("ingress").Name(ingress.Name).Body(ingress).Do().Into(result)
return
}
// Delete deletes a ingress, returns error if one occurs.
func (c *ingress) Delete(name string, options *api.DeleteOptions) (err error) {
if options == nil {
return c.r.Delete().Namespace(c.ns).Resource("ingress").Name(name).Do().Error()
}
body, err := api.Scheme.EncodeToVersion(options, c.r.APIVersion())
if err != nil {
return err
}
return c.r.Delete().Namespace(c.ns).Resource("ingress").Name(name).Body(body).Do().Error()
}
// Watch returns a watch.Interface that watches the requested ingress.
func (c *ingress) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
return c.r.Get().
Prefix("watch").
Namespace(c.ns).
Resource("ingress").
Param("resourceVersion", resourceVersion).
LabelsSelectorParam(label).
FieldsSelectorParam(field).
Watch()
}
// UpdateStatus takes the name of the ingress and the new status. Returns the server's representation of the ingress, and an error, if it occurs.
func (c *ingress) UpdateStatus(ingress *experimental.Ingress) (result *experimental.Ingress, err error) {
result = &experimental.Ingress{}
err = c.r.Put().Namespace(c.ns).Resource("ingress").Name(ingress.Name).SubResource("status").Body(ingress).Do().Into(result)
return
}

View File

@@ -0,0 +1,230 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package unversioned
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/experimental"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
)
func getIngressResourceName() string {
return "ingress"
}
func TestListIngress(t *testing.T) {
ns := api.NamespaceAll
c := &testClient{
Request: testRequest{
Method: "GET",
Path: testapi.Experimental.ResourcePath(getIngressResourceName(), ns, ""),
},
Response: Response{StatusCode: 200,
Body: &experimental.IngressList{
Items: []experimental.Ingress{
{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: experimental.IngressSpec{
Rules: []experimental.IngressRule{},
},
},
},
},
},
}
receivedIngressList, err := c.Setup(t).Experimental().Ingress(ns).List(labels.Everything(), fields.Everything())
c.Validate(t, receivedIngressList, err)
}
func TestGetIngress(t *testing.T) {
ns := api.NamespaceDefault
c := &testClient{
Request: testRequest{
Method: "GET",
Path: testapi.Experimental.ResourcePath(getIngressResourceName(), ns, "foo"),
Query: buildQueryValues(nil),
},
Response: Response{
StatusCode: 200,
Body: &experimental.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: experimental.IngressSpec{
Rules: []experimental.IngressRule{},
},
},
},
}
receivedIngress, err := c.Setup(t).Experimental().Ingress(ns).Get("foo")
c.Validate(t, receivedIngress, err)
}
func TestGetIngressWithNoName(t *testing.T) {
ns := api.NamespaceDefault
c := &testClient{Error: true}
receivedIngress, err := c.Setup(t).Experimental().Ingress(ns).Get("")
if (err != nil) && (err.Error() != nameRequiredError) {
t.Errorf("Expected error: %v, but got %v", nameRequiredError, err)
}
c.Validate(t, receivedIngress, err)
}
func TestUpdateIngress(t *testing.T) {
ns := api.NamespaceDefault
requestIngress := &experimental.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: ns,
ResourceVersion: "1",
},
}
c := &testClient{
Request: testRequest{
Method: "PUT",
Path: testapi.Experimental.ResourcePath(getIngressResourceName(), ns, "foo"),
Query: buildQueryValues(nil),
},
Response: Response{
StatusCode: 200,
Body: &experimental.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: experimental.IngressSpec{
Rules: []experimental.IngressRule{},
},
},
},
}
receivedIngress, err := c.Setup(t).Experimental().Ingress(ns).Update(requestIngress)
c.Validate(t, receivedIngress, err)
}
func TestUpdateIngressStatus(t *testing.T) {
ns := api.NamespaceDefault
lbStatus := api.LoadBalancerStatus{
Ingress: []api.LoadBalancerIngress{
{IP: "127.0.0.1"},
},
}
requestIngress := &experimental.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: ns,
ResourceVersion: "1",
},
Status: experimental.IngressStatus{
LoadBalancer: lbStatus,
},
}
c := &testClient{
Request: testRequest{
Method: "PUT",
Path: testapi.Experimental.ResourcePath(getIngressResourceName(), ns, "foo") + "/status",
Query: buildQueryValues(nil),
},
Response: Response{
StatusCode: 200,
Body: &experimental.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: experimental.IngressSpec{
Rules: []experimental.IngressRule{},
},
Status: experimental.IngressStatus{
LoadBalancer: lbStatus,
},
},
},
}
receivedIngress, err := c.Setup(t).Experimental().Ingress(ns).UpdateStatus(requestIngress)
c.Validate(t, receivedIngress, err)
}
func TestDeleteIngress(t *testing.T) {
ns := api.NamespaceDefault
c := &testClient{
Request: testRequest{
Method: "DELETE",
Path: testapi.Experimental.ResourcePath(getIngressResourceName(), ns, "foo"),
Query: buildQueryValues(nil),
},
Response: Response{StatusCode: 200},
}
err := c.Setup(t).Experimental().Ingress(ns).Delete("foo", nil)
c.Validate(t, nil, err)
}
func TestCreateIngress(t *testing.T) {
ns := api.NamespaceDefault
requestIngress := &experimental.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: ns,
},
}
c := &testClient{
Request: testRequest{
Method: "POST",
Path: testapi.Experimental.ResourcePath(getIngressResourceName(), ns, ""),
Body: requestIngress,
Query: buildQueryValues(nil),
},
Response: Response{
StatusCode: 200,
Body: &experimental.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: experimental.IngressSpec{
Rules: []experimental.IngressRule{},
},
},
},
}
receivedIngress, err := c.Setup(t).Experimental().Ingress(ns).Create(requestIngress)
c.Validate(t, receivedIngress, err)
}

View File

@@ -0,0 +1,86 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package testclient
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/experimental"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/watch"
)
// FakeIngress implements IngressInterface. Meant to be embedded into a struct to get a default
// implementation. This makes faking out just the method you want to test easier.
type FakeIngress struct {
Fake *FakeExperimental
Namespace string
}
func (c *FakeIngress) Get(name string) (*experimental.Ingress, error) {
obj, err := c.Fake.Invokes(NewGetAction("ingress", c.Namespace, name), &experimental.Ingress{})
if obj == nil {
return nil, err
}
return obj.(*experimental.Ingress), err
}
func (c *FakeIngress) List(label labels.Selector, fields fields.Selector) (*experimental.IngressList, error) {
obj, err := c.Fake.Invokes(NewListAction("ingress", c.Namespace, label, nil), &experimental.IngressList{})
if obj == nil {
return nil, err
}
return obj.(*experimental.IngressList), err
}
func (c *FakeIngress) Create(ingress *experimental.Ingress) (*experimental.Ingress, error) {
obj, err := c.Fake.Invokes(NewCreateAction("ingress", c.Namespace, ingress), ingress)
if obj == nil {
return nil, err
}
return obj.(*experimental.Ingress), err
}
func (c *FakeIngress) Update(ingress *experimental.Ingress) (*experimental.Ingress, error) {
obj, err := c.Fake.Invokes(NewUpdateAction("ingress", c.Namespace, ingress), ingress)
if obj == nil {
return nil, err
}
return obj.(*experimental.Ingress), err
}
func (c *FakeIngress) Delete(name string, options *api.DeleteOptions) error {
_, err := c.Fake.Invokes(NewDeleteAction("ingress", c.Namespace, name), &experimental.Ingress{})
return err
}
func (c *FakeIngress) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
return c.Fake.InvokesWatch(NewWatchAction("ingress", c.Namespace, label, field, resourceVersion))
}
func (c *FakeIngress) UpdateStatus(ingress *experimental.Ingress) (result *experimental.Ingress, err error) {
obj, err := c.Fake.Invokes(NewUpdateSubresourceAction("ingress", "status", c.Namespace, ingress), ingress)
if obj == nil {
return nil, err
}
return obj.(*experimental.Ingress), err
}

View File

@@ -327,3 +327,7 @@ func (c *FakeExperimental) Scales(namespace string) client.ScaleInterface {
func (c *FakeExperimental) Jobs(namespace string) client.JobInterface { func (c *FakeExperimental) Jobs(namespace string) client.JobInterface {
return &FakeJobs{Fake: c, Namespace: namespace} return &FakeJobs{Fake: c, Namespace: namespace}
} }
func (c *FakeExperimental) Ingress(namespace string) client.IngressInterface {
return &FakeIngress{Fake: c, Namespace: namespace}
}

View File

@@ -209,6 +209,10 @@ func deleteAllContent(kubeClient client.Interface, experimentalMode bool, namesp
if err != nil { if err != nil {
return estimate, err return estimate, err
} }
err = deleteIngress(kubeClient.Experimental(), namespace)
if err != nil {
return estimate, err
}
} }
return estimate, nil return estimate, nil
} }
@@ -496,3 +500,17 @@ func deleteDeployments(expClient client.ExperimentalInterface, ns string) error
} }
return nil return nil
} }
func deleteIngress(expClient client.ExperimentalInterface, ns string) error {
items, err := expClient.Ingress(ns).List(labels.Everything(), fields.Everything())
if err != nil {
return err
}
for i := range items.Items {
err := expClient.Ingress(ns).Delete(items.Items[i].Name, nil)
if err != nil && !errors.IsNotFound(err) {
return err
}
}
return nil
}

View File

@@ -114,6 +114,7 @@ func testSyncNamespaceThatIsTerminating(t *testing.T, experimentalMode bool) {
strings.Join([]string{"list", "daemonsets", ""}, "-"), strings.Join([]string{"list", "daemonsets", ""}, "-"),
strings.Join([]string{"list", "deployments", ""}, "-"), strings.Join([]string{"list", "deployments", ""}, "-"),
strings.Join([]string{"list", "jobs", ""}, "-"), strings.Join([]string{"list", "jobs", ""}, "-"),
strings.Join([]string{"list", "ingress", ""}, "-"),
) )
} }

View File

@@ -102,6 +102,7 @@ func expandResourceShortcut(resource string) string {
"rc": "replicationcontrollers", "rc": "replicationcontrollers",
"ds": "daemonsets", "ds": "daemonsets",
"svc": "services", "svc": "services",
"ing": "ingress",
} }
if expanded, ok := shortForms[resource]; ok { if expanded, ok := shortForms[resource]; ok {
return expanded return expanded

View File

@@ -45,6 +45,14 @@ import (
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/sets"
) )
const (
tabwriterMinWidth = 10
tabwriterWidth = 4
tabwriterPadding = 3
tabwriterPadChar = ' '
tabwriterFlags = 0
)
// GetPrinter takes a format type, an optional format argument. It will return true // GetPrinter takes a format type, an optional format argument. It will return true
// if the format is generic (untyped), otherwise it will return false. The printer // if the format is generic (untyped), otherwise it will return false. The printer
// is agnostic to schema versions, so you must send arguments to PrintObj in the // is agnostic to schema versions, so you must send arguments to PrintObj in the
@@ -382,6 +390,7 @@ var podTemplateColumns = []string{"TEMPLATE", "CONTAINER(S)", "IMAGE(S)", "PODLA
var replicationControllerColumns = []string{"CONTROLLER", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "REPLICAS", "AGE"} var replicationControllerColumns = []string{"CONTROLLER", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "REPLICAS", "AGE"}
var jobColumns = []string{"JOB", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "SUCCESSFUL"} var jobColumns = []string{"JOB", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "SUCCESSFUL"}
var serviceColumns = []string{"NAME", "CLUSTER_IP", "EXTERNAL_IP", "PORT(S)", "SELECTOR", "AGE"} var serviceColumns = []string{"NAME", "CLUSTER_IP", "EXTERNAL_IP", "PORT(S)", "SELECTOR", "AGE"}
var ingressColumns = []string{"NAME", "RULE", "BACKEND", "ADDRESS"}
var endpointColumns = []string{"NAME", "ENDPOINTS", "AGE"} var endpointColumns = []string{"NAME", "ENDPOINTS", "AGE"}
var nodeColumns = []string{"NAME", "LABELS", "STATUS", "AGE"} var nodeColumns = []string{"NAME", "LABELS", "STATUS", "AGE"}
var daemonSetColumns = []string{"NAME", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "NODE-SELECTOR"} var daemonSetColumns = []string{"NAME", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "NODE-SELECTOR"}
@@ -413,6 +422,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() {
h.Handler(jobColumns, printJobList) h.Handler(jobColumns, printJobList)
h.Handler(serviceColumns, printService) h.Handler(serviceColumns, printService)
h.Handler(serviceColumns, printServiceList) h.Handler(serviceColumns, printServiceList)
h.Handler(ingressColumns, printIngress)
h.Handler(ingressColumns, printIngressList)
h.Handler(endpointColumns, printEndpoints) h.Handler(endpointColumns, printEndpoints)
h.Handler(endpointColumns, printEndpointsList) h.Handler(endpointColumns, printEndpointsList)
h.Handler(nodeColumns, printNode) h.Handler(nodeColumns, printNode)
@@ -739,6 +750,18 @@ func printJobList(list *experimental.JobList, w io.Writer, withNamespace bool, w
return nil return nil
} }
// loadBalancerStatusStringer behaves just like a string interface and converts the given status to a string.
func loadBalancerStatusStringer(s api.LoadBalancerStatus) string {
ingress := s.Ingress
result := []string{}
for i := range ingress {
if ingress[i].IP != "" {
result = append(result, ingress[i].IP)
}
}
return strings.Join(result, ",")
}
func getServiceExternalIP(svc *api.Service) string { func getServiceExternalIP(svc *api.Service) string {
switch svc.Spec.Type { switch svc.Spec.Type {
case api.ServiceTypeClusterIP: case api.ServiceTypeClusterIP:
@@ -752,17 +775,12 @@ func getServiceExternalIP(svc *api.Service) string {
} }
return "nodes" return "nodes"
case api.ServiceTypeLoadBalancer: case api.ServiceTypeLoadBalancer:
ingress := svc.Status.LoadBalancer.Ingress lbIps := loadBalancerStatusStringer(svc.Status.LoadBalancer)
result := []string{}
for i := range ingress {
if ingress[i].IP != "" {
result = append(result, ingress[i].IP)
}
}
if len(svc.Spec.ExternalIPs) > 0 { if len(svc.Spec.ExternalIPs) > 0 {
result = append(result, svc.Spec.ExternalIPs...) result := append(strings.Split(lbIps, ","), svc.Spec.ExternalIPs...)
return strings.Join(result, ",")
} }
return strings.Join(result, ",") return lbIps
} }
return "unknown" return "unknown"
} }
@@ -813,6 +831,71 @@ func printServiceList(list *api.ServiceList, w io.Writer, withNamespace bool, wi
return nil return nil
} }
// backendStringer behaves just like a string interface and converts the given backend to a string.
func backendStringer(backend *experimental.IngressBackend) string {
if backend == nil {
return ""
}
return fmt.Sprintf("%v:%v", backend.ServiceName, backend.ServicePort.String())
}
func printIngress(ingress *experimental.Ingress, w io.Writer, withNamespace, wide bool, showAll bool, columnLabels []string) error {
name := ingress.Name
namespace := ingress.Namespace
hostRules := ingress.Spec.Rules
if withNamespace {
if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil {
return err
}
}
if _, err := fmt.Fprintf(w, "%s\t%v\t%v\t%v\n",
name,
"-",
backendStringer(ingress.Spec.Backend),
loadBalancerStatusStringer(ingress.Status.LoadBalancer)); err != nil {
return err
}
// Lay out all the rules on separate lines.
extraLinePrefix := ""
if withNamespace {
extraLinePrefix = "\t"
}
for _, rules := range hostRules {
if rules.HTTP == nil {
continue
}
_, err := fmt.Fprintf(w, "%s\t%v\t", extraLinePrefix, rules.Host)
if err != nil {
return err
}
if _, err := fmt.Fprint(w, appendLabelTabs(columnLabels)); err != nil {
return err
}
for _, rule := range rules.HTTP.Paths {
_, err := fmt.Fprintf(w, "%s\t%v\t%v", extraLinePrefix, rule.Path, backendStringer(&rule.Backend))
if err != nil {
return err
}
if _, err := fmt.Fprint(w, appendLabelTabs(columnLabels)); err != nil {
return err
}
}
}
return nil
}
func printIngressList(ingressList *experimental.IngressList, w io.Writer, withNamespace, wide bool, showAll bool, columnLabels []string) error {
for _, ingress := range ingressList.Items {
if err := printIngress(&ingress, w, withNamespace, wide, true, columnLabels); err != nil {
return err
}
}
return nil
}
func printDaemonSet(ds *experimental.DaemonSet, w io.Writer, withNamespace bool, wide bool, showAll bool, columnLabels []string) error { func printDaemonSet(ds *experimental.DaemonSet, w io.Writer, withNamespace bool, wide bool, showAll bool, columnLabels []string) error {
name := ds.Name name := ds.Name
namespace := ds.Namespace namespace := ds.Namespace
@@ -1369,7 +1452,7 @@ func formatWideHeaders(wide bool, t reflect.Type) []string {
// PrintObj prints the obj in a human-friendly format according to the type of the obj. // PrintObj prints the obj in a human-friendly format according to the type of the obj.
func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error { func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error {
w := tabwriter.NewWriter(output, 10, 4, 3, ' ', 0) w := tabwriter.NewWriter(output, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, tabwriterFlags)
defer w.Flush() defer w.Flush()
t := reflect.TypeOf(obj) t := reflect.TypeOf(obj)
if handler := h.handlerMap[t]; handler != nil { if handler := h.handlerMap[t]; handler != nil {

View File

@@ -55,6 +55,7 @@ import (
endpointsetcd "k8s.io/kubernetes/pkg/registry/endpoint/etcd" endpointsetcd "k8s.io/kubernetes/pkg/registry/endpoint/etcd"
eventetcd "k8s.io/kubernetes/pkg/registry/event/etcd" eventetcd "k8s.io/kubernetes/pkg/registry/event/etcd"
expcontrolleretcd "k8s.io/kubernetes/pkg/registry/experimental/controller/etcd" expcontrolleretcd "k8s.io/kubernetes/pkg/registry/experimental/controller/etcd"
ingressetcd "k8s.io/kubernetes/pkg/registry/ingress/etcd"
jobetcd "k8s.io/kubernetes/pkg/registry/job/etcd" jobetcd "k8s.io/kubernetes/pkg/registry/job/etcd"
limitrangeetcd "k8s.io/kubernetes/pkg/registry/limitrange/etcd" limitrangeetcd "k8s.io/kubernetes/pkg/registry/limitrange/etcd"
"k8s.io/kubernetes/pkg/registry/namespace" "k8s.io/kubernetes/pkg/registry/namespace"
@@ -967,6 +968,7 @@ func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion {
daemonSetStorage, daemonSetStatusStorage := daemonetcd.NewREST(c.ExpDatabaseStorage) daemonSetStorage, daemonSetStatusStorage := daemonetcd.NewREST(c.ExpDatabaseStorage)
deploymentStorage := deploymentetcd.NewStorage(c.ExpDatabaseStorage) deploymentStorage := deploymentetcd.NewStorage(c.ExpDatabaseStorage)
jobStorage, jobStatusStorage := jobetcd.NewREST(c.ExpDatabaseStorage) jobStorage, jobStatusStorage := jobetcd.NewREST(c.ExpDatabaseStorage)
ingressStorage := ingressetcd.NewREST(c.ExpDatabaseStorage)
thirdPartyControl := ThirdPartyController{ thirdPartyControl := ThirdPartyController{
master: m, master: m,
@@ -990,6 +992,7 @@ func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion {
strings.ToLower("deployments/scale"): deploymentStorage.Scale, strings.ToLower("deployments/scale"): deploymentStorage.Scale,
strings.ToLower("jobs"): jobStorage, strings.ToLower("jobs"): jobStorage,
strings.ToLower("jobs/status"): jobStatusStorage, strings.ToLower("jobs/status"): jobStatusStorage,
strings.ToLower("ingress"): ingressStorage,
} }
expMeta := latest.GroupOrDie("experimental") expMeta := latest.GroupOrDie("experimental")

View File

@@ -0,0 +1,17 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ingress

View File

@@ -0,0 +1,77 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcd
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/experimental"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/generic"
etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd"
ingress "k8s.io/kubernetes/pkg/registry/ingress"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage"
)
const (
IngressPath string = "/ingress"
)
// rest implements a RESTStorage for replication controllers against etcd
type REST struct {
*etcdgeneric.Etcd
}
// NewREST returns a RESTStorage object that will work against replication controllers.
func NewREST(s storage.Interface) *REST {
store := &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &experimental.Ingress{} },
// NewListFunc returns an object capable of storing results of an etcd list.
NewListFunc: func() runtime.Object { return &experimental.IngressList{} },
// Produces a ingress that etcd understands, to the root of the resource
// by combining the namespace in the context with the given prefix
KeyRootFunc: func(ctx api.Context) string {
return etcdgeneric.NamespaceKeyRootFunc(ctx, IngressPath)
},
// Produces a ingress that etcd understands, to the resource by combining
// the namespace in the context with the given prefix
KeyFunc: func(ctx api.Context, name string) (string, error) {
return etcdgeneric.NamespaceKeyFunc(ctx, IngressPath, name)
},
// Retrieve the name field of a replication controller
ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*experimental.Ingress).Name, nil
},
// Used to match objects based on labels/fields for list and watch
PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher {
return ingress.MatchIngress(label, field)
},
EndpointName: "ingress",
// Used to validate controller creation
CreateStrategy: ingress.Strategy,
// Used to validate controller updates
UpdateStrategy: ingress.Strategy,
Storage: s,
}
return &REST{store}
}

View File

@@ -0,0 +1,203 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcd
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/experimental"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/tools"
"k8s.io/kubernetes/pkg/util"
)
func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient) {
etcdStorage, fakeClient := registrytest.NewEtcdStorage(t, "experimental")
ingressStorage := NewREST(etcdStorage)
return ingressStorage, fakeClient
}
var (
namespace = api.NamespaceNone
name = "foo-ingress"
defaultHostname = "foo.bar.com"
defaultBackendName = "default-backend"
defaultBackendPort = util.IntOrString{Kind: util.IntstrInt, IntVal: 80}
defaultLoadBalancer = "127.0.0.1"
defaultPath = "/foo"
defaultPathMap = map[string]string{defaultPath: defaultBackendName}
)
type IngressRuleValues map[string]string
func toHTTPIngressPaths(pathMap map[string]string) []experimental.HTTPIngressPath {
httpPaths := []experimental.HTTPIngressPath{}
for path, backend := range pathMap {
httpPaths = append(httpPaths, experimental.HTTPIngressPath{
Path: path,
Backend: experimental.IngressBackend{
ServiceName: backend,
ServicePort: defaultBackendPort,
},
})
}
return httpPaths
}
func toIngressRules(hostRules map[string]IngressRuleValues) []experimental.IngressRule {
rules := []experimental.IngressRule{}
for host, pathMap := range hostRules {
rules = append(rules, experimental.IngressRule{
Host: host,
IngressRuleValue: experimental.IngressRuleValue{
HTTP: &experimental.HTTPIngressRuleValue{
Paths: toHTTPIngressPaths(pathMap),
},
},
})
}
return rules
}
func newIngress(pathMap map[string]string) *experimental.Ingress {
return &experimental.Ingress{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: experimental.IngressSpec{
Backend: &experimental.IngressBackend{
ServiceName: defaultBackendName,
ServicePort: defaultBackendPort,
},
Rules: toIngressRules(map[string]IngressRuleValues{
defaultHostname: pathMap,
}),
},
Status: experimental.IngressStatus{
LoadBalancer: api.LoadBalancerStatus{
Ingress: []api.LoadBalancerIngress{
{IP: defaultLoadBalancer},
},
},
},
}
}
func validIngress() *experimental.Ingress {
return newIngress(defaultPathMap)
}
func TestCreate(t *testing.T) {
storage, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
ingress := validIngress()
noDefaultBackendAndRules := validIngress()
noDefaultBackendAndRules.Spec.Backend = &experimental.IngressBackend{}
noDefaultBackendAndRules.Spec.Rules = []experimental.IngressRule{}
badPath := validIngress()
badPath.Spec.Rules = toIngressRules(map[string]IngressRuleValues{
"foo.bar.com": {"/invalid[": "svc"}})
test.TestCreate(
// valid
ingress,
noDefaultBackendAndRules,
badPath,
)
}
func TestUpdate(t *testing.T) {
storage, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestUpdate(
// valid
validIngress(),
// updateFunc
func(obj runtime.Object) runtime.Object {
object := obj.(*experimental.Ingress)
object.Spec.Rules = toIngressRules(map[string]IngressRuleValues{
"bar.foo.com": {"/bar": defaultBackendName},
})
return object
},
// invalid updateFunc: ObjeceMeta is not to be tampered with.
func(obj runtime.Object) runtime.Object {
object := obj.(*experimental.Ingress)
object.UID = "newUID"
return object
},
func(obj runtime.Object) runtime.Object {
object := obj.(*experimental.Ingress)
object.Name = ""
return object
},
func(obj runtime.Object) runtime.Object {
object := obj.(*experimental.Ingress)
object.Spec.Rules = toIngressRules(map[string]IngressRuleValues{
"foo.bar.com": {"/invalid[": "svc"}})
return object
},
)
}
func TestDelete(t *testing.T) {
storage, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestDelete(validIngress())
}
func TestGet(t *testing.T) {
storage, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestGet(validIngress())
}
func TestList(t *testing.T) {
storage, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestList(validIngress())
}
func TestWatch(t *testing.T) {
storage, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestWatch(
validIngress(),
// matching labels
[]labels.Set{},
// not matching labels
[]labels.Set{
{"a": "c"},
{"foo": "bar"},
},
// matching fields
[]fields.Set{
{"metadata.name": name},
},
// not matching fields
[]fields.Set{
{"metadata.name": "bar"},
{"name": name},
},
)
}

View File

@@ -0,0 +1,116 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ingress
import (
"fmt"
"reflect"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/experimental"
"k8s.io/kubernetes/pkg/apis/experimental/validation"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/generic"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/fielderrors"
)
// ingressStrategy implements verification logic for Replication Ingresss.
type ingressStrategy struct {
runtime.ObjectTyper
api.NameGenerator
}
// Strategy is the default logic that applies when creating and updating Replication Ingress objects.
var Strategy = ingressStrategy{api.Scheme, api.SimpleNameGenerator}
// NamespaceScoped returns true because all Ingress' need to be within a namespace.
func (ingressStrategy) NamespaceScoped() bool {
return true
}
// PrepareForCreate clears the status of an Ingress before creation.
func (ingressStrategy) PrepareForCreate(obj runtime.Object) {
ingress := obj.(*experimental.Ingress)
ingress.Status = experimental.IngressStatus{}
ingress.Generation = 1
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (ingressStrategy) PrepareForUpdate(obj, old runtime.Object) {
newIngress := obj.(*experimental.Ingress)
oldIngress := old.(*experimental.Ingress)
//TODO: Clear Ingress status once we have a sub-resource.
// Any changes to the spec increment the generation number, any changes to the
// status should reflect the generation number of the corresponding object.
// See api.ObjectMeta description for more information on Generation.
if !reflect.DeepEqual(oldIngress.Spec, newIngress.Spec) {
newIngress.Generation = oldIngress.Generation + 1
}
}
// Validate validates a new Ingress.
func (ingressStrategy) Validate(ctx api.Context, obj runtime.Object) fielderrors.ValidationErrorList {
ingress := obj.(*experimental.Ingress)
err := validation.ValidateIngress(ingress)
return err
}
// AllowCreateOnUpdate is false for Ingress; this means POST is needed to create one.
func (ingressStrategy) AllowCreateOnUpdate() bool {
return false
}
// ValidateUpdate is the default update validation for an end user.
func (ingressStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList {
validationErrorList := validation.ValidateIngress(obj.(*experimental.Ingress))
updateErrorList := validation.ValidateIngressUpdate(old.(*experimental.Ingress), obj.(*experimental.Ingress))
return append(validationErrorList, updateErrorList...)
}
// AllowUnconditionalUpdate is the default update policy for Ingress objects.
func (ingressStrategy) AllowUnconditionalUpdate() bool {
return true
}
// IngressToSelectableFields returns a label set that represents the object.
func IngressToSelectableFields(ingress *experimental.Ingress) fields.Set {
return fields.Set{
"metadata.name": ingress.Name,
}
}
// MatchIngress is the filter used by the generic etcd backend to ingress
// watch events from etcd to clients of the apiserver only interested in specific
// labels/fields.
func MatchIngress(label labels.Selector, field fields.Selector) generic.Matcher {
return &generic.SelectionPredicate{
Label: label,
Field: field,
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
ingress, ok := obj.(*experimental.Ingress)
if !ok {
return nil, nil, fmt.Errorf("Given object is not an Ingress.")
}
return labels.Set(ingress.ObjectMeta.Labels), IngressToSelectableFields(ingress), nil
},
}
}