When a router sees a route, it should make a decision about the name it
wishes to assign to that route (based on its override-hostname or own
settings) and then write the decision back to the route status with an
effective host value (so clients can see what the actual route value
is). If multiple routers attempt to make conflicting writes, have them
remember the conflict so as to avoid battles as they race to the new
value. Errors due to route uniqueness are also written back to the
status.
Each route has an array of RouteIngress structs, which contain an array
of conditions, the routerName (passed via --name to the router), and the
effective host.
Add printers and describers, make ingress empty be a special case.
| ... | ... |
@@ -21971,7 +21971,67 @@ |
| 21971 | 21971 |
}, |
| 21972 | 21972 |
"v1.RouteStatus": {
|
| 21973 | 21973 |
"id": "v1.RouteStatus", |
| 21974 |
- "properties": {}
|
|
| 21974 |
+ "required": [ |
|
| 21975 |
+ "ingress" |
|
| 21976 |
+ ], |
|
| 21977 |
+ "properties": {
|
|
| 21978 |
+ "ingress": {
|
|
| 21979 |
+ "type": "array", |
|
| 21980 |
+ "items": {
|
|
| 21981 |
+ "$ref": "v1.RouteIngress" |
|
| 21982 |
+ }, |
|
| 21983 |
+ "description": "traffic reaches this route via these ingress paths" |
|
| 21984 |
+ } |
|
| 21985 |
+ } |
|
| 21986 |
+ }, |
|
| 21987 |
+ "v1.RouteIngress": {
|
|
| 21988 |
+ "id": "v1.RouteIngress", |
|
| 21989 |
+ "properties": {
|
|
| 21990 |
+ "host": {
|
|
| 21991 |
+ "type": "string", |
|
| 21992 |
+ "description": "the host name this route is exposed to by the specified router" |
|
| 21993 |
+ }, |
|
| 21994 |
+ "routerName": {
|
|
| 21995 |
+ "type": "string", |
|
| 21996 |
+ "description": "the name of the router exposing this route" |
|
| 21997 |
+ }, |
|
| 21998 |
+ "conditions": {
|
|
| 21999 |
+ "type": "array", |
|
| 22000 |
+ "items": {
|
|
| 22001 |
+ "$ref": "v1.RouteIngressCondition" |
|
| 22002 |
+ }, |
|
| 22003 |
+ "description": "the conditions that apply to this router" |
|
| 22004 |
+ } |
|
| 22005 |
+ } |
|
| 22006 |
+ }, |
|
| 22007 |
+ "v1.RouteIngressCondition": {
|
|
| 22008 |
+ "id": "v1.RouteIngressCondition", |
|
| 22009 |
+ "required": [ |
|
| 22010 |
+ "type", |
|
| 22011 |
+ "status" |
|
| 22012 |
+ ], |
|
| 22013 |
+ "properties": {
|
|
| 22014 |
+ "type": {
|
|
| 22015 |
+ "type": "string", |
|
| 22016 |
+ "description": "the type of the condition" |
|
| 22017 |
+ }, |
|
| 22018 |
+ "status": {
|
|
| 22019 |
+ "type": "string", |
|
| 22020 |
+ "description": "status is the status of the condition; True, False, or Unknown" |
|
| 22021 |
+ }, |
|
| 22022 |
+ "reason": {
|
|
| 22023 |
+ "type": "string", |
|
| 22024 |
+ "description": "brief reason for the condition's last transition, machine readable constant" |
|
| 22025 |
+ }, |
|
| 22026 |
+ "message": {
|
|
| 22027 |
+ "type": "string", |
|
| 22028 |
+ "description": "human readable message indicating details about this condition" |
|
| 22029 |
+ }, |
|
| 22030 |
+ "lastTransitionTime": {
|
|
| 22031 |
+ "type": "string", |
|
| 22032 |
+ "description": "the last time at which this condition transitioned to the current status" |
|
| 22033 |
+ } |
|
| 22034 |
+ } |
|
| 21975 | 22035 |
}, |
| 21976 | 22036 |
"v1.SubjectAccessReview": {
|
| 21977 | 22037 |
"id": "v1.SubjectAccessReview", |
| ... | ... |
@@ -17675,6 +17675,7 @@ _openshift_infra_router() |
| 17675 | 17675 |
flags+=("--kubernetes=")
|
| 17676 | 17676 |
flags+=("--labels=")
|
| 17677 | 17677 |
flags+=("--master=")
|
| 17678 |
+ flags+=("--name=")
|
|
| 17678 | 17679 |
flags+=("--namespace=")
|
| 17679 | 17680 |
two_word_flags+=("-n")
|
| 17680 | 17681 |
flags+=("--namespace-labels=")
|
| ... | ... |
@@ -17755,6 +17756,7 @@ _openshift_infra_f5-router() |
| 17755 | 17755 |
flags+=("--kubernetes=")
|
| 17756 | 17756 |
flags+=("--labels=")
|
| 17757 | 17757 |
flags+=("--master=")
|
| 17758 |
+ flags+=("--name=")
|
|
| 17758 | 17759 |
flags+=("--namespace=")
|
| 17759 | 17760 |
two_word_flags+=("-n")
|
| 17760 | 17761 |
flags+=("--namespace-labels=")
|
| ... | ... |
@@ -2717,6 +2717,39 @@ func deepCopy_api_Route(in routeapi.Route, out *routeapi.Route, c *conversion.Cl |
| 2717 | 2717 |
return nil |
| 2718 | 2718 |
} |
| 2719 | 2719 |
|
| 2720 |
+func deepCopy_api_RouteIngress(in routeapi.RouteIngress, out *routeapi.RouteIngress, c *conversion.Cloner) error {
|
|
| 2721 |
+ out.Host = in.Host |
|
| 2722 |
+ out.RouterName = in.RouterName |
|
| 2723 |
+ if in.Conditions != nil {
|
|
| 2724 |
+ out.Conditions = make([]routeapi.RouteIngressCondition, len(in.Conditions)) |
|
| 2725 |
+ for i := range in.Conditions {
|
|
| 2726 |
+ if err := deepCopy_api_RouteIngressCondition(in.Conditions[i], &out.Conditions[i], c); err != nil {
|
|
| 2727 |
+ return err |
|
| 2728 |
+ } |
|
| 2729 |
+ } |
|
| 2730 |
+ } else {
|
|
| 2731 |
+ out.Conditions = nil |
|
| 2732 |
+ } |
|
| 2733 |
+ return nil |
|
| 2734 |
+} |
|
| 2735 |
+ |
|
| 2736 |
+func deepCopy_api_RouteIngressCondition(in routeapi.RouteIngressCondition, out *routeapi.RouteIngressCondition, c *conversion.Cloner) error {
|
|
| 2737 |
+ out.Type = in.Type |
|
| 2738 |
+ out.Status = in.Status |
|
| 2739 |
+ out.Reason = in.Reason |
|
| 2740 |
+ out.Message = in.Message |
|
| 2741 |
+ if in.LastTransitionTime != nil {
|
|
| 2742 |
+ if newVal, err := c.DeepCopy(in.LastTransitionTime); err != nil {
|
|
| 2743 |
+ return err |
|
| 2744 |
+ } else {
|
|
| 2745 |
+ out.LastTransitionTime = newVal.(*unversioned.Time) |
|
| 2746 |
+ } |
|
| 2747 |
+ } else {
|
|
| 2748 |
+ out.LastTransitionTime = nil |
|
| 2749 |
+ } |
|
| 2750 |
+ return nil |
|
| 2751 |
+} |
|
| 2752 |
+ |
|
| 2720 | 2753 |
func deepCopy_api_RouteList(in routeapi.RouteList, out *routeapi.RouteList, c *conversion.Cloner) error {
|
| 2721 | 2754 |
if newVal, err := c.DeepCopy(in.TypeMeta); err != nil {
|
| 2722 | 2755 |
return err |
| ... | ... |
@@ -2778,6 +2811,16 @@ func deepCopy_api_RouteSpec(in routeapi.RouteSpec, out *routeapi.RouteSpec, c *c |
| 2778 | 2778 |
} |
| 2779 | 2779 |
|
| 2780 | 2780 |
func deepCopy_api_RouteStatus(in routeapi.RouteStatus, out *routeapi.RouteStatus, c *conversion.Cloner) error {
|
| 2781 |
+ if in.Ingress != nil {
|
|
| 2782 |
+ out.Ingress = make([]routeapi.RouteIngress, len(in.Ingress)) |
|
| 2783 |
+ for i := range in.Ingress {
|
|
| 2784 |
+ if err := deepCopy_api_RouteIngress(in.Ingress[i], &out.Ingress[i], c); err != nil {
|
|
| 2785 |
+ return err |
|
| 2786 |
+ } |
|
| 2787 |
+ } |
|
| 2788 |
+ } else {
|
|
| 2789 |
+ out.Ingress = nil |
|
| 2790 |
+ } |
|
| 2781 | 2791 |
return nil |
| 2782 | 2792 |
} |
| 2783 | 2793 |
|
| ... | ... |
@@ -3287,6 +3330,8 @@ func init() {
|
| 3287 | 3287 |
deepCopy_api_ProjectSpec, |
| 3288 | 3288 |
deepCopy_api_ProjectStatus, |
| 3289 | 3289 |
deepCopy_api_Route, |
| 3290 |
+ deepCopy_api_RouteIngress, |
|
| 3291 |
+ deepCopy_api_RouteIngressCondition, |
|
| 3290 | 3292 |
deepCopy_api_RouteList, |
| 3291 | 3293 |
deepCopy_api_RoutePort, |
| 3292 | 3294 |
deepCopy_api_RouteSpec, |
| ... | ... |
@@ -5026,6 +5026,53 @@ func Convert_api_Route_To_v1_Route(in *routeapi.Route, out *routeapiv1.Route, s |
| 5026 | 5026 |
return autoConvert_api_Route_To_v1_Route(in, out, s) |
| 5027 | 5027 |
} |
| 5028 | 5028 |
|
| 5029 |
+func autoConvert_api_RouteIngress_To_v1_RouteIngress(in *routeapi.RouteIngress, out *routeapiv1.RouteIngress, s conversion.Scope) error {
|
|
| 5030 |
+ if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
|
| 5031 |
+ defaulting.(func(*routeapi.RouteIngress))(in) |
|
| 5032 |
+ } |
|
| 5033 |
+ out.Host = in.Host |
|
| 5034 |
+ out.RouterName = in.RouterName |
|
| 5035 |
+ if in.Conditions != nil {
|
|
| 5036 |
+ out.Conditions = make([]routeapiv1.RouteIngressCondition, len(in.Conditions)) |
|
| 5037 |
+ for i := range in.Conditions {
|
|
| 5038 |
+ if err := Convert_api_RouteIngressCondition_To_v1_RouteIngressCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil {
|
|
| 5039 |
+ return err |
|
| 5040 |
+ } |
|
| 5041 |
+ } |
|
| 5042 |
+ } else {
|
|
| 5043 |
+ out.Conditions = nil |
|
| 5044 |
+ } |
|
| 5045 |
+ return nil |
|
| 5046 |
+} |
|
| 5047 |
+ |
|
| 5048 |
+func Convert_api_RouteIngress_To_v1_RouteIngress(in *routeapi.RouteIngress, out *routeapiv1.RouteIngress, s conversion.Scope) error {
|
|
| 5049 |
+ return autoConvert_api_RouteIngress_To_v1_RouteIngress(in, out, s) |
|
| 5050 |
+} |
|
| 5051 |
+ |
|
| 5052 |
+func autoConvert_api_RouteIngressCondition_To_v1_RouteIngressCondition(in *routeapi.RouteIngressCondition, out *routeapiv1.RouteIngressCondition, s conversion.Scope) error {
|
|
| 5053 |
+ if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
|
| 5054 |
+ defaulting.(func(*routeapi.RouteIngressCondition))(in) |
|
| 5055 |
+ } |
|
| 5056 |
+ out.Type = routeapiv1.RouteIngressConditionType(in.Type) |
|
| 5057 |
+ out.Status = apiv1.ConditionStatus(in.Status) |
|
| 5058 |
+ out.Reason = in.Reason |
|
| 5059 |
+ out.Message = in.Message |
|
| 5060 |
+ // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time |
|
| 5061 |
+ if in.LastTransitionTime != nil {
|
|
| 5062 |
+ out.LastTransitionTime = new(unversioned.Time) |
|
| 5063 |
+ if err := api.Convert_unversioned_Time_To_unversioned_Time(in.LastTransitionTime, out.LastTransitionTime, s); err != nil {
|
|
| 5064 |
+ return err |
|
| 5065 |
+ } |
|
| 5066 |
+ } else {
|
|
| 5067 |
+ out.LastTransitionTime = nil |
|
| 5068 |
+ } |
|
| 5069 |
+ return nil |
|
| 5070 |
+} |
|
| 5071 |
+ |
|
| 5072 |
+func Convert_api_RouteIngressCondition_To_v1_RouteIngressCondition(in *routeapi.RouteIngressCondition, out *routeapiv1.RouteIngressCondition, s conversion.Scope) error {
|
|
| 5073 |
+ return autoConvert_api_RouteIngressCondition_To_v1_RouteIngressCondition(in, out, s) |
|
| 5074 |
+} |
|
| 5075 |
+ |
|
| 5029 | 5076 |
func autoConvert_api_RouteList_To_v1_RouteList(in *routeapi.RouteList, out *routeapiv1.RouteList, s conversion.Scope) error {
|
| 5030 | 5077 |
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
| 5031 | 5078 |
defaulting.(func(*routeapi.RouteList))(in) |
| ... | ... |
@@ -5102,6 +5149,16 @@ func autoConvert_api_RouteStatus_To_v1_RouteStatus(in *routeapi.RouteStatus, out |
| 5102 | 5102 |
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
| 5103 | 5103 |
defaulting.(func(*routeapi.RouteStatus))(in) |
| 5104 | 5104 |
} |
| 5105 |
+ if in.Ingress != nil {
|
|
| 5106 |
+ out.Ingress = make([]routeapiv1.RouteIngress, len(in.Ingress)) |
|
| 5107 |
+ for i := range in.Ingress {
|
|
| 5108 |
+ if err := Convert_api_RouteIngress_To_v1_RouteIngress(&in.Ingress[i], &out.Ingress[i], s); err != nil {
|
|
| 5109 |
+ return err |
|
| 5110 |
+ } |
|
| 5111 |
+ } |
|
| 5112 |
+ } else {
|
|
| 5113 |
+ out.Ingress = nil |
|
| 5114 |
+ } |
|
| 5105 | 5115 |
return nil |
| 5106 | 5116 |
} |
| 5107 | 5117 |
|
| ... | ... |
@@ -5146,6 +5203,53 @@ func Convert_v1_Route_To_api_Route(in *routeapiv1.Route, out *routeapi.Route, s |
| 5146 | 5146 |
return autoConvert_v1_Route_To_api_Route(in, out, s) |
| 5147 | 5147 |
} |
| 5148 | 5148 |
|
| 5149 |
+func autoConvert_v1_RouteIngress_To_api_RouteIngress(in *routeapiv1.RouteIngress, out *routeapi.RouteIngress, s conversion.Scope) error {
|
|
| 5150 |
+ if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
|
| 5151 |
+ defaulting.(func(*routeapiv1.RouteIngress))(in) |
|
| 5152 |
+ } |
|
| 5153 |
+ out.Host = in.Host |
|
| 5154 |
+ out.RouterName = in.RouterName |
|
| 5155 |
+ if in.Conditions != nil {
|
|
| 5156 |
+ out.Conditions = make([]routeapi.RouteIngressCondition, len(in.Conditions)) |
|
| 5157 |
+ for i := range in.Conditions {
|
|
| 5158 |
+ if err := Convert_v1_RouteIngressCondition_To_api_RouteIngressCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil {
|
|
| 5159 |
+ return err |
|
| 5160 |
+ } |
|
| 5161 |
+ } |
|
| 5162 |
+ } else {
|
|
| 5163 |
+ out.Conditions = nil |
|
| 5164 |
+ } |
|
| 5165 |
+ return nil |
|
| 5166 |
+} |
|
| 5167 |
+ |
|
| 5168 |
+func Convert_v1_RouteIngress_To_api_RouteIngress(in *routeapiv1.RouteIngress, out *routeapi.RouteIngress, s conversion.Scope) error {
|
|
| 5169 |
+ return autoConvert_v1_RouteIngress_To_api_RouteIngress(in, out, s) |
|
| 5170 |
+} |
|
| 5171 |
+ |
|
| 5172 |
+func autoConvert_v1_RouteIngressCondition_To_api_RouteIngressCondition(in *routeapiv1.RouteIngressCondition, out *routeapi.RouteIngressCondition, s conversion.Scope) error {
|
|
| 5173 |
+ if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
|
| 5174 |
+ defaulting.(func(*routeapiv1.RouteIngressCondition))(in) |
|
| 5175 |
+ } |
|
| 5176 |
+ out.Type = routeapi.RouteIngressConditionType(in.Type) |
|
| 5177 |
+ out.Status = api.ConditionStatus(in.Status) |
|
| 5178 |
+ out.Reason = in.Reason |
|
| 5179 |
+ out.Message = in.Message |
|
| 5180 |
+ // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time |
|
| 5181 |
+ if in.LastTransitionTime != nil {
|
|
| 5182 |
+ out.LastTransitionTime = new(unversioned.Time) |
|
| 5183 |
+ if err := api.Convert_unversioned_Time_To_unversioned_Time(in.LastTransitionTime, out.LastTransitionTime, s); err != nil {
|
|
| 5184 |
+ return err |
|
| 5185 |
+ } |
|
| 5186 |
+ } else {
|
|
| 5187 |
+ out.LastTransitionTime = nil |
|
| 5188 |
+ } |
|
| 5189 |
+ return nil |
|
| 5190 |
+} |
|
| 5191 |
+ |
|
| 5192 |
+func Convert_v1_RouteIngressCondition_To_api_RouteIngressCondition(in *routeapiv1.RouteIngressCondition, out *routeapi.RouteIngressCondition, s conversion.Scope) error {
|
|
| 5193 |
+ return autoConvert_v1_RouteIngressCondition_To_api_RouteIngressCondition(in, out, s) |
|
| 5194 |
+} |
|
| 5195 |
+ |
|
| 5149 | 5196 |
func autoConvert_v1_RouteList_To_api_RouteList(in *routeapiv1.RouteList, out *routeapi.RouteList, s conversion.Scope) error {
|
| 5150 | 5197 |
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
| 5151 | 5198 |
defaulting.(func(*routeapiv1.RouteList))(in) |
| ... | ... |
@@ -5222,6 +5326,16 @@ func autoConvert_v1_RouteStatus_To_api_RouteStatus(in *routeapiv1.RouteStatus, o |
| 5222 | 5222 |
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
| 5223 | 5223 |
defaulting.(func(*routeapiv1.RouteStatus))(in) |
| 5224 | 5224 |
} |
| 5225 |
+ if in.Ingress != nil {
|
|
| 5226 |
+ out.Ingress = make([]routeapi.RouteIngress, len(in.Ingress)) |
|
| 5227 |
+ for i := range in.Ingress {
|
|
| 5228 |
+ if err := Convert_v1_RouteIngress_To_api_RouteIngress(&in.Ingress[i], &out.Ingress[i], s); err != nil {
|
|
| 5229 |
+ return err |
|
| 5230 |
+ } |
|
| 5231 |
+ } |
|
| 5232 |
+ } else {
|
|
| 5233 |
+ out.Ingress = nil |
|
| 5234 |
+ } |
|
| 5225 | 5235 |
return nil |
| 5226 | 5236 |
} |
| 5227 | 5237 |
|
| ... | ... |
@@ -8444,6 +8558,8 @@ func init() {
|
| 8444 | 8444 |
autoConvert_api_RoleList_To_v1_RoleList, |
| 8445 | 8445 |
autoConvert_api_Role_To_v1_Role, |
| 8446 | 8446 |
autoConvert_api_RollingDeploymentStrategyParams_To_v1_RollingDeploymentStrategyParams, |
| 8447 |
+ autoConvert_api_RouteIngressCondition_To_v1_RouteIngressCondition, |
|
| 8448 |
+ autoConvert_api_RouteIngress_To_v1_RouteIngress, |
|
| 8447 | 8449 |
autoConvert_api_RouteList_To_v1_RouteList, |
| 8448 | 8450 |
autoConvert_api_RoutePort_To_v1_RoutePort, |
| 8449 | 8451 |
autoConvert_api_RouteSpec_To_v1_RouteSpec, |
| ... | ... |
@@ -8613,6 +8729,8 @@ func init() {
|
| 8613 | 8613 |
autoConvert_v1_RoleList_To_api_RoleList, |
| 8614 | 8614 |
autoConvert_v1_Role_To_api_Role, |
| 8615 | 8615 |
autoConvert_v1_RollingDeploymentStrategyParams_To_api_RollingDeploymentStrategyParams, |
| 8616 |
+ autoConvert_v1_RouteIngressCondition_To_api_RouteIngressCondition, |
|
| 8617 |
+ autoConvert_v1_RouteIngress_To_api_RouteIngress, |
|
| 8616 | 8618 |
autoConvert_v1_RouteList_To_api_RouteList, |
| 8617 | 8619 |
autoConvert_v1_RoutePort_To_api_RoutePort, |
| 8618 | 8620 |
autoConvert_v1_RouteSpec_To_api_RouteSpec, |
| ... | ... |
@@ -2604,6 +2604,39 @@ func deepCopy_v1_Route(in routeapiv1.Route, out *routeapiv1.Route, c *conversion |
| 2604 | 2604 |
return nil |
| 2605 | 2605 |
} |
| 2606 | 2606 |
|
| 2607 |
+func deepCopy_v1_RouteIngress(in routeapiv1.RouteIngress, out *routeapiv1.RouteIngress, c *conversion.Cloner) error {
|
|
| 2608 |
+ out.Host = in.Host |
|
| 2609 |
+ out.RouterName = in.RouterName |
|
| 2610 |
+ if in.Conditions != nil {
|
|
| 2611 |
+ out.Conditions = make([]routeapiv1.RouteIngressCondition, len(in.Conditions)) |
|
| 2612 |
+ for i := range in.Conditions {
|
|
| 2613 |
+ if err := deepCopy_v1_RouteIngressCondition(in.Conditions[i], &out.Conditions[i], c); err != nil {
|
|
| 2614 |
+ return err |
|
| 2615 |
+ } |
|
| 2616 |
+ } |
|
| 2617 |
+ } else {
|
|
| 2618 |
+ out.Conditions = nil |
|
| 2619 |
+ } |
|
| 2620 |
+ return nil |
|
| 2621 |
+} |
|
| 2622 |
+ |
|
| 2623 |
+func deepCopy_v1_RouteIngressCondition(in routeapiv1.RouteIngressCondition, out *routeapiv1.RouteIngressCondition, c *conversion.Cloner) error {
|
|
| 2624 |
+ out.Type = in.Type |
|
| 2625 |
+ out.Status = in.Status |
|
| 2626 |
+ out.Reason = in.Reason |
|
| 2627 |
+ out.Message = in.Message |
|
| 2628 |
+ if in.LastTransitionTime != nil {
|
|
| 2629 |
+ if newVal, err := c.DeepCopy(in.LastTransitionTime); err != nil {
|
|
| 2630 |
+ return err |
|
| 2631 |
+ } else {
|
|
| 2632 |
+ out.LastTransitionTime = newVal.(*unversioned.Time) |
|
| 2633 |
+ } |
|
| 2634 |
+ } else {
|
|
| 2635 |
+ out.LastTransitionTime = nil |
|
| 2636 |
+ } |
|
| 2637 |
+ return nil |
|
| 2638 |
+} |
|
| 2639 |
+ |
|
| 2607 | 2640 |
func deepCopy_v1_RouteList(in routeapiv1.RouteList, out *routeapiv1.RouteList, c *conversion.Cloner) error {
|
| 2608 | 2641 |
if newVal, err := c.DeepCopy(in.TypeMeta); err != nil {
|
| 2609 | 2642 |
return err |
| ... | ... |
@@ -2665,6 +2698,16 @@ func deepCopy_v1_RouteSpec(in routeapiv1.RouteSpec, out *routeapiv1.RouteSpec, c |
| 2665 | 2665 |
} |
| 2666 | 2666 |
|
| 2667 | 2667 |
func deepCopy_v1_RouteStatus(in routeapiv1.RouteStatus, out *routeapiv1.RouteStatus, c *conversion.Cloner) error {
|
| 2668 |
+ if in.Ingress != nil {
|
|
| 2669 |
+ out.Ingress = make([]routeapiv1.RouteIngress, len(in.Ingress)) |
|
| 2670 |
+ for i := range in.Ingress {
|
|
| 2671 |
+ if err := deepCopy_v1_RouteIngress(in.Ingress[i], &out.Ingress[i], c); err != nil {
|
|
| 2672 |
+ return err |
|
| 2673 |
+ } |
|
| 2674 |
+ } |
|
| 2675 |
+ } else {
|
|
| 2676 |
+ out.Ingress = nil |
|
| 2677 |
+ } |
|
| 2668 | 2678 |
return nil |
| 2669 | 2679 |
} |
| 2670 | 2680 |
|
| ... | ... |
@@ -3174,6 +3217,8 @@ func init() {
|
| 3174 | 3174 |
deepCopy_v1_ProjectSpec, |
| 3175 | 3175 |
deepCopy_v1_ProjectStatus, |
| 3176 | 3176 |
deepCopy_v1_Route, |
| 3177 |
+ deepCopy_v1_RouteIngress, |
|
| 3178 |
+ deepCopy_v1_RouteIngressCondition, |
|
| 3177 | 3179 |
deepCopy_v1_RouteList, |
| 3178 | 3180 |
deepCopy_v1_RoutePort, |
| 3179 | 3181 |
deepCopy_v1_RouteSpec, |
| ... | ... |
@@ -3891,6 +3891,53 @@ func Convert_api_Route_To_v1beta3_Route(in *routeapi.Route, out *routeapiv1beta3 |
| 3891 | 3891 |
return autoConvert_api_Route_To_v1beta3_Route(in, out, s) |
| 3892 | 3892 |
} |
| 3893 | 3893 |
|
| 3894 |
+func autoConvert_api_RouteIngress_To_v1beta3_RouteIngress(in *routeapi.RouteIngress, out *routeapiv1beta3.RouteIngress, s conversion.Scope) error {
|
|
| 3895 |
+ if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
|
| 3896 |
+ defaulting.(func(*routeapi.RouteIngress))(in) |
|
| 3897 |
+ } |
|
| 3898 |
+ out.Host = in.Host |
|
| 3899 |
+ out.RouterName = in.RouterName |
|
| 3900 |
+ if in.Conditions != nil {
|
|
| 3901 |
+ out.Conditions = make([]routeapiv1beta3.RouteIngressCondition, len(in.Conditions)) |
|
| 3902 |
+ for i := range in.Conditions {
|
|
| 3903 |
+ if err := Convert_api_RouteIngressCondition_To_v1beta3_RouteIngressCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil {
|
|
| 3904 |
+ return err |
|
| 3905 |
+ } |
|
| 3906 |
+ } |
|
| 3907 |
+ } else {
|
|
| 3908 |
+ out.Conditions = nil |
|
| 3909 |
+ } |
|
| 3910 |
+ return nil |
|
| 3911 |
+} |
|
| 3912 |
+ |
|
| 3913 |
+func Convert_api_RouteIngress_To_v1beta3_RouteIngress(in *routeapi.RouteIngress, out *routeapiv1beta3.RouteIngress, s conversion.Scope) error {
|
|
| 3914 |
+ return autoConvert_api_RouteIngress_To_v1beta3_RouteIngress(in, out, s) |
|
| 3915 |
+} |
|
| 3916 |
+ |
|
| 3917 |
+func autoConvert_api_RouteIngressCondition_To_v1beta3_RouteIngressCondition(in *routeapi.RouteIngressCondition, out *routeapiv1beta3.RouteIngressCondition, s conversion.Scope) error {
|
|
| 3918 |
+ if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
|
| 3919 |
+ defaulting.(func(*routeapi.RouteIngressCondition))(in) |
|
| 3920 |
+ } |
|
| 3921 |
+ out.Type = routeapiv1beta3.RouteIngressConditionType(in.Type) |
|
| 3922 |
+ out.Status = apiv1beta3.ConditionStatus(in.Status) |
|
| 3923 |
+ out.Reason = in.Reason |
|
| 3924 |
+ out.Message = in.Message |
|
| 3925 |
+ // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time |
|
| 3926 |
+ if in.LastTransitionTime != nil {
|
|
| 3927 |
+ out.LastTransitionTime = new(unversioned.Time) |
|
| 3928 |
+ if err := api.Convert_unversioned_Time_To_unversioned_Time(in.LastTransitionTime, out.LastTransitionTime, s); err != nil {
|
|
| 3929 |
+ return err |
|
| 3930 |
+ } |
|
| 3931 |
+ } else {
|
|
| 3932 |
+ out.LastTransitionTime = nil |
|
| 3933 |
+ } |
|
| 3934 |
+ return nil |
|
| 3935 |
+} |
|
| 3936 |
+ |
|
| 3937 |
+func Convert_api_RouteIngressCondition_To_v1beta3_RouteIngressCondition(in *routeapi.RouteIngressCondition, out *routeapiv1beta3.RouteIngressCondition, s conversion.Scope) error {
|
|
| 3938 |
+ return autoConvert_api_RouteIngressCondition_To_v1beta3_RouteIngressCondition(in, out, s) |
|
| 3939 |
+} |
|
| 3940 |
+ |
|
| 3894 | 3941 |
func autoConvert_api_RouteList_To_v1beta3_RouteList(in *routeapi.RouteList, out *routeapiv1beta3.RouteList, s conversion.Scope) error {
|
| 3895 | 3942 |
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
| 3896 | 3943 |
defaulting.(func(*routeapi.RouteList))(in) |
| ... | ... |
@@ -3967,6 +4014,16 @@ func autoConvert_api_RouteStatus_To_v1beta3_RouteStatus(in *routeapi.RouteStatus |
| 3967 | 3967 |
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
| 3968 | 3968 |
defaulting.(func(*routeapi.RouteStatus))(in) |
| 3969 | 3969 |
} |
| 3970 |
+ if in.Ingress != nil {
|
|
| 3971 |
+ out.Ingress = make([]routeapiv1beta3.RouteIngress, len(in.Ingress)) |
|
| 3972 |
+ for i := range in.Ingress {
|
|
| 3973 |
+ if err := Convert_api_RouteIngress_To_v1beta3_RouteIngress(&in.Ingress[i], &out.Ingress[i], s); err != nil {
|
|
| 3974 |
+ return err |
|
| 3975 |
+ } |
|
| 3976 |
+ } |
|
| 3977 |
+ } else {
|
|
| 3978 |
+ out.Ingress = nil |
|
| 3979 |
+ } |
|
| 3970 | 3980 |
return nil |
| 3971 | 3981 |
} |
| 3972 | 3982 |
|
| ... | ... |
@@ -4011,6 +4068,53 @@ func Convert_v1beta3_Route_To_api_Route(in *routeapiv1beta3.Route, out *routeapi |
| 4011 | 4011 |
return autoConvert_v1beta3_Route_To_api_Route(in, out, s) |
| 4012 | 4012 |
} |
| 4013 | 4013 |
|
| 4014 |
+func autoConvert_v1beta3_RouteIngress_To_api_RouteIngress(in *routeapiv1beta3.RouteIngress, out *routeapi.RouteIngress, s conversion.Scope) error {
|
|
| 4015 |
+ if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
|
| 4016 |
+ defaulting.(func(*routeapiv1beta3.RouteIngress))(in) |
|
| 4017 |
+ } |
|
| 4018 |
+ out.Host = in.Host |
|
| 4019 |
+ out.RouterName = in.RouterName |
|
| 4020 |
+ if in.Conditions != nil {
|
|
| 4021 |
+ out.Conditions = make([]routeapi.RouteIngressCondition, len(in.Conditions)) |
|
| 4022 |
+ for i := range in.Conditions {
|
|
| 4023 |
+ if err := Convert_v1beta3_RouteIngressCondition_To_api_RouteIngressCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil {
|
|
| 4024 |
+ return err |
|
| 4025 |
+ } |
|
| 4026 |
+ } |
|
| 4027 |
+ } else {
|
|
| 4028 |
+ out.Conditions = nil |
|
| 4029 |
+ } |
|
| 4030 |
+ return nil |
|
| 4031 |
+} |
|
| 4032 |
+ |
|
| 4033 |
+func Convert_v1beta3_RouteIngress_To_api_RouteIngress(in *routeapiv1beta3.RouteIngress, out *routeapi.RouteIngress, s conversion.Scope) error {
|
|
| 4034 |
+ return autoConvert_v1beta3_RouteIngress_To_api_RouteIngress(in, out, s) |
|
| 4035 |
+} |
|
| 4036 |
+ |
|
| 4037 |
+func autoConvert_v1beta3_RouteIngressCondition_To_api_RouteIngressCondition(in *routeapiv1beta3.RouteIngressCondition, out *routeapi.RouteIngressCondition, s conversion.Scope) error {
|
|
| 4038 |
+ if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
|
| 4039 |
+ defaulting.(func(*routeapiv1beta3.RouteIngressCondition))(in) |
|
| 4040 |
+ } |
|
| 4041 |
+ out.Type = routeapi.RouteIngressConditionType(in.Type) |
|
| 4042 |
+ out.Status = api.ConditionStatus(in.Status) |
|
| 4043 |
+ out.Reason = in.Reason |
|
| 4044 |
+ out.Message = in.Message |
|
| 4045 |
+ // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time |
|
| 4046 |
+ if in.LastTransitionTime != nil {
|
|
| 4047 |
+ out.LastTransitionTime = new(unversioned.Time) |
|
| 4048 |
+ if err := api.Convert_unversioned_Time_To_unversioned_Time(in.LastTransitionTime, out.LastTransitionTime, s); err != nil {
|
|
| 4049 |
+ return err |
|
| 4050 |
+ } |
|
| 4051 |
+ } else {
|
|
| 4052 |
+ out.LastTransitionTime = nil |
|
| 4053 |
+ } |
|
| 4054 |
+ return nil |
|
| 4055 |
+} |
|
| 4056 |
+ |
|
| 4057 |
+func Convert_v1beta3_RouteIngressCondition_To_api_RouteIngressCondition(in *routeapiv1beta3.RouteIngressCondition, out *routeapi.RouteIngressCondition, s conversion.Scope) error {
|
|
| 4058 |
+ return autoConvert_v1beta3_RouteIngressCondition_To_api_RouteIngressCondition(in, out, s) |
|
| 4059 |
+} |
|
| 4060 |
+ |
|
| 4014 | 4061 |
func autoConvert_v1beta3_RouteList_To_api_RouteList(in *routeapiv1beta3.RouteList, out *routeapi.RouteList, s conversion.Scope) error {
|
| 4015 | 4062 |
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
| 4016 | 4063 |
defaulting.(func(*routeapiv1beta3.RouteList))(in) |
| ... | ... |
@@ -4087,6 +4191,16 @@ func autoConvert_v1beta3_RouteStatus_To_api_RouteStatus(in *routeapiv1beta3.Rout |
| 4087 | 4087 |
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
| 4088 | 4088 |
defaulting.(func(*routeapiv1beta3.RouteStatus))(in) |
| 4089 | 4089 |
} |
| 4090 |
+ if in.Ingress != nil {
|
|
| 4091 |
+ out.Ingress = make([]routeapi.RouteIngress, len(in.Ingress)) |
|
| 4092 |
+ for i := range in.Ingress {
|
|
| 4093 |
+ if err := Convert_v1beta3_RouteIngress_To_api_RouteIngress(&in.Ingress[i], &out.Ingress[i], s); err != nil {
|
|
| 4094 |
+ return err |
|
| 4095 |
+ } |
|
| 4096 |
+ } |
|
| 4097 |
+ } else {
|
|
| 4098 |
+ out.Ingress = nil |
|
| 4099 |
+ } |
|
| 4090 | 4100 |
return nil |
| 4091 | 4101 |
} |
| 4092 | 4102 |
|
| ... | ... |
@@ -6747,6 +6861,8 @@ func init() {
|
| 6747 | 6747 |
autoConvert_api_RoleList_To_v1beta3_RoleList, |
| 6748 | 6748 |
autoConvert_api_Role_To_v1beta3_Role, |
| 6749 | 6749 |
autoConvert_api_RollingDeploymentStrategyParams_To_v1beta3_RollingDeploymentStrategyParams, |
| 6750 |
+ autoConvert_api_RouteIngressCondition_To_v1beta3_RouteIngressCondition, |
|
| 6751 |
+ autoConvert_api_RouteIngress_To_v1beta3_RouteIngress, |
|
| 6750 | 6752 |
autoConvert_api_RouteList_To_v1beta3_RouteList, |
| 6751 | 6753 |
autoConvert_api_RoutePort_To_v1beta3_RoutePort, |
| 6752 | 6754 |
autoConvert_api_RouteSpec_To_v1beta3_RouteSpec, |
| ... | ... |
@@ -6888,6 +7004,8 @@ func init() {
|
| 6888 | 6888 |
autoConvert_v1beta3_RoleList_To_api_RoleList, |
| 6889 | 6889 |
autoConvert_v1beta3_Role_To_api_Role, |
| 6890 | 6890 |
autoConvert_v1beta3_RollingDeploymentStrategyParams_To_api_RollingDeploymentStrategyParams, |
| 6891 |
+ autoConvert_v1beta3_RouteIngressCondition_To_api_RouteIngressCondition, |
|
| 6892 |
+ autoConvert_v1beta3_RouteIngress_To_api_RouteIngress, |
|
| 6891 | 6893 |
autoConvert_v1beta3_RouteList_To_api_RouteList, |
| 6892 | 6894 |
autoConvert_v1beta3_RoutePort_To_api_RoutePort, |
| 6893 | 6895 |
autoConvert_v1beta3_RouteSpec_To_api_RouteSpec, |
| ... | ... |
@@ -2441,6 +2441,39 @@ func deepCopy_v1beta3_Route(in routeapiv1beta3.Route, out *routeapiv1beta3.Route |
| 2441 | 2441 |
return nil |
| 2442 | 2442 |
} |
| 2443 | 2443 |
|
| 2444 |
+func deepCopy_v1beta3_RouteIngress(in routeapiv1beta3.RouteIngress, out *routeapiv1beta3.RouteIngress, c *conversion.Cloner) error {
|
|
| 2445 |
+ out.Host = in.Host |
|
| 2446 |
+ out.RouterName = in.RouterName |
|
| 2447 |
+ if in.Conditions != nil {
|
|
| 2448 |
+ out.Conditions = make([]routeapiv1beta3.RouteIngressCondition, len(in.Conditions)) |
|
| 2449 |
+ for i := range in.Conditions {
|
|
| 2450 |
+ if err := deepCopy_v1beta3_RouteIngressCondition(in.Conditions[i], &out.Conditions[i], c); err != nil {
|
|
| 2451 |
+ return err |
|
| 2452 |
+ } |
|
| 2453 |
+ } |
|
| 2454 |
+ } else {
|
|
| 2455 |
+ out.Conditions = nil |
|
| 2456 |
+ } |
|
| 2457 |
+ return nil |
|
| 2458 |
+} |
|
| 2459 |
+ |
|
| 2460 |
+func deepCopy_v1beta3_RouteIngressCondition(in routeapiv1beta3.RouteIngressCondition, out *routeapiv1beta3.RouteIngressCondition, c *conversion.Cloner) error {
|
|
| 2461 |
+ out.Type = in.Type |
|
| 2462 |
+ out.Status = in.Status |
|
| 2463 |
+ out.Reason = in.Reason |
|
| 2464 |
+ out.Message = in.Message |
|
| 2465 |
+ if in.LastTransitionTime != nil {
|
|
| 2466 |
+ if newVal, err := c.DeepCopy(in.LastTransitionTime); err != nil {
|
|
| 2467 |
+ return err |
|
| 2468 |
+ } else {
|
|
| 2469 |
+ out.LastTransitionTime = newVal.(*unversioned.Time) |
|
| 2470 |
+ } |
|
| 2471 |
+ } else {
|
|
| 2472 |
+ out.LastTransitionTime = nil |
|
| 2473 |
+ } |
|
| 2474 |
+ return nil |
|
| 2475 |
+} |
|
| 2476 |
+ |
|
| 2444 | 2477 |
func deepCopy_v1beta3_RouteList(in routeapiv1beta3.RouteList, out *routeapiv1beta3.RouteList, c *conversion.Cloner) error {
|
| 2445 | 2478 |
if newVal, err := c.DeepCopy(in.TypeMeta); err != nil {
|
| 2446 | 2479 |
return err |
| ... | ... |
@@ -2502,6 +2535,16 @@ func deepCopy_v1beta3_RouteSpec(in routeapiv1beta3.RouteSpec, out *routeapiv1bet |
| 2502 | 2502 |
} |
| 2503 | 2503 |
|
| 2504 | 2504 |
func deepCopy_v1beta3_RouteStatus(in routeapiv1beta3.RouteStatus, out *routeapiv1beta3.RouteStatus, c *conversion.Cloner) error {
|
| 2505 |
+ if in.Ingress != nil {
|
|
| 2506 |
+ out.Ingress = make([]routeapiv1beta3.RouteIngress, len(in.Ingress)) |
|
| 2507 |
+ for i := range in.Ingress {
|
|
| 2508 |
+ if err := deepCopy_v1beta3_RouteIngress(in.Ingress[i], &out.Ingress[i], c); err != nil {
|
|
| 2509 |
+ return err |
|
| 2510 |
+ } |
|
| 2511 |
+ } |
|
| 2512 |
+ } else {
|
|
| 2513 |
+ out.Ingress = nil |
|
| 2514 |
+ } |
|
| 2505 | 2515 |
return nil |
| 2506 | 2516 |
} |
| 2507 | 2517 |
|
| ... | ... |
@@ -3004,6 +3047,8 @@ func init() {
|
| 3004 | 3004 |
deepCopy_v1beta3_ProjectSpec, |
| 3005 | 3005 |
deepCopy_v1beta3_ProjectStatus, |
| 3006 | 3006 |
deepCopy_v1beta3_Route, |
| 3007 |
+ deepCopy_v1beta3_RouteIngress, |
|
| 3008 |
+ deepCopy_v1beta3_RouteIngressCondition, |
|
| 3007 | 3009 |
deepCopy_v1beta3_RouteList, |
| 3008 | 3010 |
deepCopy_v1beta3_RoutePort, |
| 3009 | 3011 |
deepCopy_v1beta3_RouteSpec, |
| ... | ... |
@@ -18,6 +18,7 @@ type RouteInterface interface {
|
| 18 | 18 |
Get(name string) (*routeapi.Route, error) |
| 19 | 19 |
Create(route *routeapi.Route) (*routeapi.Route, error) |
| 20 | 20 |
Update(route *routeapi.Route) (*routeapi.Route, error) |
| 21 |
+ UpdateStatus(route *routeapi.Route) (*routeapi.Route, error) |
|
| 21 | 22 |
Delete(name string) error |
| 22 | 23 |
Watch(opts kapi.ListOptions) (watch.Interface, error) |
| 23 | 24 |
} |
| ... | ... |
@@ -74,6 +75,13 @@ func (c *routes) Update(route *routeapi.Route) (result *routeapi.Route, err erro |
| 74 | 74 |
return |
| 75 | 75 |
} |
| 76 | 76 |
|
| 77 |
+// UpdateStatus takes the route with altered status. Returns the server's representation of the route, and an error, if it occurs. |
|
| 78 |
+func (c *routes) UpdateStatus(route *routeapi.Route) (result *routeapi.Route, err error) {
|
|
| 79 |
+ result = &routeapi.Route{}
|
|
| 80 |
+ err = c.r.Put().Namespace(c.ns).Resource("routes").Name(route.Name).SubResource("status").Body(route).Do().Into(result)
|
|
| 81 |
+ return |
|
| 82 |
+} |
|
| 83 |
+ |
|
| 77 | 84 |
// Watch returns a watch.Interface that watches the requested routes. |
| 78 | 85 |
func (c *routes) Watch(opts kapi.ListOptions) (watch.Interface, error) {
|
| 79 | 86 |
return c.r.Get(). |
| ... | ... |
@@ -51,6 +51,17 @@ func (c *FakeRoutes) Update(inObj *routeapi.Route) (*routeapi.Route, error) {
|
| 51 | 51 |
return obj.(*routeapi.Route), err |
| 52 | 52 |
} |
| 53 | 53 |
|
| 54 |
+func (c *FakeRoutes) UpdateStatus(inObj *routeapi.Route) (*routeapi.Route, error) {
|
|
| 55 |
+ action := ktestclient.NewUpdateAction("routes", c.Namespace, inObj)
|
|
| 56 |
+ action.Subresource = "status" |
|
| 57 |
+ obj, err := c.Fake.Invokes(action, inObj) |
|
| 58 |
+ if obj == nil {
|
|
| 59 |
+ return nil, err |
|
| 60 |
+ } |
|
| 61 |
+ |
|
| 62 |
+ return obj.(*routeapi.Route), err |
|
| 63 |
+} |
|
| 64 |
+ |
|
| 54 | 65 |
func (c *FakeRoutes) Delete(name string) error {
|
| 55 | 66 |
_, err := c.Fake.Invokes(ktestclient.NewDeleteAction("routes", c.Namespace, name), &routeapi.Route{})
|
| 56 | 67 |
return err |
| ... | ... |
@@ -595,9 +595,56 @@ func (d *RouteDescriber) Describe(namespace, name string) (string, error) {
|
| 595 | 595 |
|
| 596 | 596 |
return tabbedString(func(out *tabwriter.Writer) error {
|
| 597 | 597 |
formatMeta(out, route.ObjectMeta) |
| 598 |
- formatString(out, "Host", route.Spec.Host) |
|
| 598 |
+ if len(route.Spec.Host) > 0 {
|
|
| 599 |
+ formatString(out, "Requested Host", route.Spec.Host) |
|
| 600 |
+ for _, ingress := range route.Status.Ingress {
|
|
| 601 |
+ if route.Spec.Host != ingress.Host {
|
|
| 602 |
+ continue |
|
| 603 |
+ } |
|
| 604 |
+ switch status, condition := ingressConditionStatus(&ingress, routeapi.RouteAdmitted); status {
|
|
| 605 |
+ case kapi.ConditionTrue: |
|
| 606 |
+ fmt.Fprintf(out, "\t exposed on router %s %s ago\n", ingress.RouterName, strings.ToLower(formatRelativeTime(condition.LastTransitionTime.Time))) |
|
| 607 |
+ case kapi.ConditionFalse: |
|
| 608 |
+ fmt.Fprintf(out, "\t rejected by router %s: %s (%s ago)\n", ingress.RouterName, condition.Reason, strings.ToLower(formatRelativeTime(condition.LastTransitionTime.Time))) |
|
| 609 |
+ if len(condition.Message) > 0 {
|
|
| 610 |
+ fmt.Fprintf(out, "\t %s\n", condition.Message) |
|
| 611 |
+ } |
|
| 612 |
+ } |
|
| 613 |
+ } |
|
| 614 |
+ } else {
|
|
| 615 |
+ formatString(out, "Requested Host", "<auto>") |
|
| 616 |
+ } |
|
| 617 |
+ for _, ingress := range route.Status.Ingress {
|
|
| 618 |
+ if route.Spec.Host == ingress.Host {
|
|
| 619 |
+ continue |
|
| 620 |
+ } |
|
| 621 |
+ switch status, condition := ingressConditionStatus(&ingress, routeapi.RouteAdmitted); status {
|
|
| 622 |
+ case kapi.ConditionTrue: |
|
| 623 |
+ fmt.Fprintf(out, "\t%s exposed on router %s %s ago\n", ingress.Host, ingress.RouterName, strings.ToLower(formatRelativeTime(condition.LastTransitionTime.Time))) |
|
| 624 |
+ case kapi.ConditionFalse: |
|
| 625 |
+ fmt.Fprintf(out, "\trejected by router %s: %s (%s ago)\n", ingress.RouterName, condition.Reason, strings.ToLower(formatRelativeTime(condition.LastTransitionTime.Time))) |
|
| 626 |
+ if len(condition.Message) > 0 {
|
|
| 627 |
+ fmt.Fprintf(out, "\t %s\n", condition.Message) |
|
| 628 |
+ } |
|
| 629 |
+ } |
|
| 630 |
+ } |
|
| 599 | 631 |
formatString(out, "Path", route.Spec.Path) |
| 632 |
+ |
|
| 633 |
+ tlsTerm := "" |
|
| 634 |
+ insecurePolicy := "" |
|
| 635 |
+ if route.Spec.TLS != nil {
|
|
| 636 |
+ tlsTerm = string(route.Spec.TLS.Termination) |
|
| 637 |
+ insecurePolicy = string(route.Spec.TLS.InsecureEdgeTerminationPolicy) |
|
| 638 |
+ } |
|
| 639 |
+ formatString(out, "TLS Termination", tlsTerm) |
|
| 640 |
+ formatString(out, "Insecure Policy", insecurePolicy) |
|
| 641 |
+ |
|
| 600 | 642 |
formatString(out, "Service", route.Spec.To.Name) |
| 643 |
+ if route.Spec.Port != nil {
|
|
| 644 |
+ formatString(out, "Endpoint Port", route.Spec.Port.TargetPort.String()) |
|
| 645 |
+ } else {
|
|
| 646 |
+ formatString(out, "Endpoint Port", "<all endpoint ports>") |
|
| 647 |
+ } |
|
| 601 | 648 |
|
| 602 | 649 |
ends := "<none>" |
| 603 | 650 |
if endsErr != nil {
|
| ... | ... |
@@ -619,21 +666,12 @@ func (d *RouteDescriber) Describe(namespace, name string) (string, error) {
|
| 619 | 619 |
} |
| 620 | 620 |
} |
| 621 | 621 |
} |
| 622 |
- ends = strings.Join(list, ",") |
|
| 622 |
+ ends = strings.Join(list, ", ") |
|
| 623 | 623 |
if count > max {
|
| 624 | 624 |
ends += fmt.Sprintf(" + %d more...", count-max)
|
| 625 | 625 |
} |
| 626 | 626 |
} |
| 627 | 627 |
formatString(out, "Endpoints", ends) |
| 628 |
- |
|
| 629 |
- tlsTerm := "" |
|
| 630 |
- insecurePolicy := "" |
|
| 631 |
- if route.Spec.TLS != nil {
|
|
| 632 |
- tlsTerm = string(route.Spec.TLS.Termination) |
|
| 633 |
- insecurePolicy = string(route.Spec.TLS.InsecureEdgeTerminationPolicy) |
|
| 634 |
- } |
|
| 635 |
- formatString(out, "TLS Termination", tlsTerm) |
|
| 636 |
- formatString(out, "Insecure Policy", insecurePolicy) |
|
| 637 | 628 |
return nil |
| 638 | 629 |
}) |
| 639 | 630 |
} |
| ... | ... |
@@ -9,6 +9,7 @@ import ( |
| 9 | 9 |
"text/tabwriter" |
| 10 | 10 |
"time" |
| 11 | 11 |
|
| 12 |
+ kapi "k8s.io/kubernetes/pkg/api" |
|
| 12 | 13 |
"k8s.io/kubernetes/pkg/api/unversioned" |
| 13 | 14 |
kctl "k8s.io/kubernetes/pkg/kubectl" |
| 14 | 15 |
"k8s.io/kubernetes/pkg/labels" |
| ... | ... |
@@ -34,7 +35,7 @@ var ( |
| 34 | 34 |
imageStreamImageColumns = []string{"NAME", "DOCKER REF", "UPDATED", "IMAGENAME"}
|
| 35 | 35 |
imageStreamColumns = []string{"NAME", "DOCKER REPO", "TAGS", "UPDATED"}
|
| 36 | 36 |
projectColumns = []string{"NAME", "DISPLAY NAME", "STATUS"}
|
| 37 |
- routeColumns = []string{"NAME", "HOST/PORT", "PATH", "SERVICE", "LABELS", "INSECURE POLICY", "TLS TERMINATION"}
|
|
| 37 |
+ routeColumns = []string{"NAME", "HOST/PORT", "PATH", "SERVICE", "TERMINATION", "LABELS"}
|
|
| 38 | 38 |
deploymentColumns = []string{"NAME", "STATUS", "CAUSE"}
|
| 39 | 39 |
deploymentConfigColumns = []string{"NAME", "REVISION", "REPLICAS", "TRIGGERED BY"}
|
| 40 | 40 |
templateColumns = []string{"NAME", "DESCRIPTION", "PARAMETERS", "OBJECTS"}
|
| ... | ... |
@@ -425,6 +426,16 @@ func printProjectList(projects *projectapi.ProjectList, w io.Writer, opts kctl.P |
| 425 | 425 |
return nil |
| 426 | 426 |
} |
| 427 | 427 |
|
| 428 |
+func ingressConditionStatus(ingress *routeapi.RouteIngress, t routeapi.RouteIngressConditionType) (kapi.ConditionStatus, routeapi.RouteIngressCondition) {
|
|
| 429 |
+ for _, condition := range ingress.Conditions {
|
|
| 430 |
+ if t != condition.Type {
|
|
| 431 |
+ continue |
|
| 432 |
+ } |
|
| 433 |
+ return condition.Status, condition |
|
| 434 |
+ } |
|
| 435 |
+ return kapi.ConditionUnknown, routeapi.RouteIngressCondition{}
|
|
| 436 |
+} |
|
| 437 |
+ |
|
| 428 | 438 |
func printRoute(route *routeapi.Route, w io.Writer, opts kctl.PrintOptions) error {
|
| 429 | 439 |
tlsTerm := "" |
| 430 | 440 |
insecurePolicy := "" |
| ... | ... |
@@ -437,8 +448,57 @@ func printRoute(route *routeapi.Route, w io.Writer, opts kctl.PrintOptions) erro |
| 437 | 437 |
return err |
| 438 | 438 |
} |
| 439 | 439 |
} |
| 440 |
- _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", |
|
| 441 |
- route.Name, route.Spec.Host, route.Spec.Path, route.Spec.To.Name, labels.Set(route.Labels), insecurePolicy, tlsTerm) |
|
| 440 |
+ var ( |
|
| 441 |
+ matchedHost bool |
|
| 442 |
+ reason string |
|
| 443 |
+ host = route.Spec.Host |
|
| 444 |
+ |
|
| 445 |
+ admitted, errors = 0, 0 |
|
| 446 |
+ ) |
|
| 447 |
+ for _, ingress := range route.Status.Ingress {
|
|
| 448 |
+ switch status, condition := ingressConditionStatus(&ingress, routeapi.RouteAdmitted); status {
|
|
| 449 |
+ case kapi.ConditionTrue: |
|
| 450 |
+ admitted++ |
|
| 451 |
+ if !matchedHost {
|
|
| 452 |
+ matchedHost = ingress.Host == route.Spec.Host |
|
| 453 |
+ host = ingress.Host |
|
| 454 |
+ } |
|
| 455 |
+ case kapi.ConditionFalse: |
|
| 456 |
+ reason = condition.Reason |
|
| 457 |
+ errors++ |
|
| 458 |
+ } |
|
| 459 |
+ } |
|
| 460 |
+ switch {
|
|
| 461 |
+ case route.Status.Ingress == nil: |
|
| 462 |
+ // this is the legacy case, we should continue to show the host when talking to servers |
|
| 463 |
+ // that have not set status ingress, since we can't distinguish this condition from there |
|
| 464 |
+ // being no routers. |
|
| 465 |
+ case admitted == 0 && errors > 0: |
|
| 466 |
+ host = reason |
|
| 467 |
+ case errors > 0: |
|
| 468 |
+ host = fmt.Sprintf("%s ... %d rejected", host, errors)
|
|
| 469 |
+ case admitted == 0: |
|
| 470 |
+ host = "Pending" |
|
| 471 |
+ case admitted > 1: |
|
| 472 |
+ host = fmt.Sprintf("%s ... %d more", host, admitted-1)
|
|
| 473 |
+ } |
|
| 474 |
+ var policy string |
|
| 475 |
+ switch {
|
|
| 476 |
+ case len(tlsTerm) != 0 && len(insecurePolicy) != 0: |
|
| 477 |
+ policy = fmt.Sprintf("%s/%s", tlsTerm, insecurePolicy)
|
|
| 478 |
+ case len(tlsTerm) != 0: |
|
| 479 |
+ policy = tlsTerm |
|
| 480 |
+ case len(insecurePolicy) != 0: |
|
| 481 |
+ policy = fmt.Sprintf("default/%s", insecurePolicy)
|
|
| 482 |
+ default: |
|
| 483 |
+ policy = "" |
|
| 484 |
+ } |
|
| 485 |
+ svc := route.Spec.To.Name |
|
| 486 |
+ if route.Spec.Port != nil {
|
|
| 487 |
+ svc = fmt.Sprintf("%s:%s", svc, route.Spec.Port.TargetPort.String())
|
|
| 488 |
+ } |
|
| 489 |
+ _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", |
|
| 490 |
+ route.Name, host, route.Spec.Path, svc, policy, labels.Set(route.Labels)) |
|
| 442 | 491 |
return err |
| 443 | 492 |
} |
| 444 | 493 |
|
| ... | ... |
@@ -40,6 +40,8 @@ type F5RouterOptions struct {
|
| 40 | 40 |
|
| 41 | 41 |
// F5Router is the config necessary to start an F5 router plugin. |
| 42 | 42 |
type F5Router struct {
|
| 43 |
+ RouterName string |
|
| 44 |
+ |
|
| 43 | 45 |
// Host specifies the hostname or IP address of the F5 BIG-IP host. |
| 44 | 46 |
Host string |
| 45 | 47 |
|
| ... | ... |
@@ -76,6 +78,7 @@ type F5Router struct {
|
| 76 | 76 |
|
| 77 | 77 |
// Bind binds F5Router arguments to flags |
| 78 | 78 |
func (o *F5Router) Bind(flag *pflag.FlagSet) {
|
| 79 |
+ flag.StringVar(&o.RouterName, "name", util.Env("ROUTER_SERVICE_NAME", "public"), "The name the router will identify itself with in the route status")
|
|
| 79 | 80 |
flag.StringVar(&o.Host, "f5-host", util.Env("ROUTER_EXTERNAL_HOST_HOSTNAME", ""), "The host of F5 BIG-IP's management interface")
|
| 80 | 81 |
flag.StringVar(&o.Username, "f5-username", util.Env("ROUTER_EXTERNAL_HOST_USERNAME", ""), "The username for F5 BIG-IP's management utility")
|
| 81 | 82 |
flag.StringVar(&o.Password, "f5-password", util.Env("ROUTER_EXTERNAL_HOST_PASSWORD", ""), "The password for F5 BIG-IP's management utility")
|
| ... | ... |
@@ -167,13 +170,14 @@ func (o *F5RouterOptions) Run() error {
|
| 167 | 167 |
return err |
| 168 | 168 |
} |
| 169 | 169 |
|
| 170 |
- plugin := controller.NewUniqueHost(f5Plugin, o.RouteSelectionFunc()) |
|
| 171 |
- |
|
| 172 | 170 |
oc, kc, err := o.Config.Clients() |
| 173 | 171 |
if err != nil {
|
| 174 | 172 |
return err |
| 175 | 173 |
} |
| 176 | 174 |
|
| 175 |
+ statusPlugin := controller.NewStatusAdmitter(f5Plugin, oc, o.RouterName) |
|
| 176 |
+ plugin := controller.NewUniqueHost(statusPlugin, o.RouteSelectionFunc(), statusPlugin) |
|
| 177 |
+ |
|
| 177 | 178 |
factory := o.RouterSelection.NewFactory(oc, kc) |
| 178 | 179 |
controller := factory.Create(plugin) |
| 179 | 180 |
controller.Run() |
| ... | ... |
@@ -45,6 +45,7 @@ type TemplateRouterOptions struct {
|
| 45 | 45 |
} |
| 46 | 46 |
|
| 47 | 47 |
type TemplateRouter struct {
|
| 48 |
+ RouterName string |
|
| 48 | 49 |
WorkingDir string |
| 49 | 50 |
TemplateFile string |
| 50 | 51 |
ReloadScript string |
| ... | ... |
@@ -54,6 +55,7 @@ type TemplateRouter struct {
|
| 54 | 54 |
} |
| 55 | 55 |
|
| 56 | 56 |
func (o *TemplateRouter) Bind(flag *pflag.FlagSet) {
|
| 57 |
+ flag.StringVar(&o.RouterName, "name", util.Env("ROUTER_SERVICE_NAME", "public"), "The name the router will identify itself with in the route status")
|
|
| 57 | 58 |
flag.StringVar(&o.WorkingDir, "working-dir", "/var/lib/containers/router", "The working directory for the router plugin") |
| 58 | 59 |
flag.StringVar(&o.DefaultCertificate, "default-certificate", util.Env("DEFAULT_CERTIFICATE", ""), "A path to default certificate to use for routes that don't expose a TLS server cert; in PEM format")
|
| 59 | 60 |
flag.StringVar(&o.TemplateFile, "template", util.Env("TEMPLATE_FILE", ""), "The path to the template file to use")
|
| ... | ... |
@@ -139,6 +141,9 @@ func (o *TemplateRouterOptions) Complete() error {
|
| 139 | 139 |
} |
| 140 | 140 |
|
| 141 | 141 |
func (o *TemplateRouterOptions) Validate() error {
|
| 142 |
+ if len(o.RouterName) == 0 {
|
|
| 143 |
+ return errors.New("router must have a name to identify itself in route status")
|
|
| 144 |
+ } |
|
| 142 | 145 |
if len(o.TemplateFile) == 0 {
|
| 143 | 146 |
return errors.New("template file must be specified")
|
| 144 | 147 |
} |
| ... | ... |
@@ -169,13 +174,14 @@ func (o *TemplateRouterOptions) Run() error {
|
| 169 | 169 |
return err |
| 170 | 170 |
} |
| 171 | 171 |
|
| 172 |
- plugin := controller.NewUniqueHost(templatePlugin, o.RouteSelectionFunc()) |
|
| 173 |
- |
|
| 174 | 172 |
oc, kc, err := o.Config.Clients() |
| 175 | 173 |
if err != nil {
|
| 176 | 174 |
return err |
| 177 | 175 |
} |
| 178 | 176 |
|
| 177 |
+ statusPlugin := controller.NewStatusAdmitter(templatePlugin, oc, o.RouterName) |
|
| 178 |
+ plugin := controller.NewUniqueHost(statusPlugin, o.RouteSelectionFunc(), statusPlugin) |
|
| 179 |
+ |
|
| 179 | 180 |
factory := o.RouterSelection.NewFactory(oc, kc) |
| 180 | 181 |
controller := factory.Create(plugin) |
| 181 | 182 |
controller.Run() |
| ... | ... |
@@ -117,6 +117,11 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
|
| 117 | 117 |
// this is used by verifyImageStreamAccess in pkg/dockerregistry/server/auth.go |
| 118 | 118 |
Resources: sets.NewString("imagestreams/layers"),
|
| 119 | 119 |
}, |
| 120 |
+ // an admin can run routers that write back conditions to the route |
|
| 121 |
+ {
|
|
| 122 |
+ Verbs: sets.NewString("update"),
|
|
| 123 |
+ Resources: sets.NewString("routes/status"),
|
|
| 124 |
+ }, |
|
| 120 | 125 |
}, |
| 121 | 126 |
}, |
| 122 | 127 |
{
|
| ... | ... |
@@ -323,6 +328,11 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
|
| 323 | 323 |
Verbs: sets.NewString("list", "watch"),
|
| 324 | 324 |
Resources: sets.NewString("routes", "endpoints"),
|
| 325 | 325 |
}, |
| 326 |
+ // routers write back conditions to the route |
|
| 327 |
+ {
|
|
| 328 |
+ Verbs: sets.NewString("update"),
|
|
| 329 |
+ Resources: sets.NewString("routes/status"),
|
|
| 330 |
+ }, |
|
| 326 | 331 |
}, |
| 327 | 332 |
}, |
| 328 | 333 |
{
|
| ... | ... |
@@ -49,6 +49,49 @@ type RoutePort struct {
|
| 49 | 49 |
// RouteStatus provides relevant info about the status of a route, including which routers |
| 50 | 50 |
// acknowledge it. |
| 51 | 51 |
type RouteStatus struct {
|
| 52 |
+ // Ingress describes the places where the route may be exposed. The list of |
|
| 53 |
+ // ingress points may contain duplicate Host or RouterName values. Routes |
|
| 54 |
+ // are considered live once they are `Ready` |
|
| 55 |
+ Ingress []RouteIngress |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+// RouteIngress holds information about the places where a route is exposed |
|
| 59 |
+type RouteIngress struct {
|
|
| 60 |
+ // Host is the host string under which the route is exposed; this value is required |
|
| 61 |
+ Host string |
|
| 62 |
+ // Name is a name chosen by the router to identify itself; this value is required |
|
| 63 |
+ RouterName string |
|
| 64 |
+ // Conditions is the state of the route, may be empty. |
|
| 65 |
+ Conditions []RouteIngressCondition |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+// RouteIngressConditionType is a valid value for RouteCondition |
|
| 69 |
+type RouteIngressConditionType string |
|
| 70 |
+ |
|
| 71 |
+// These are valid conditions of pod. |
|
| 72 |
+const ( |
|
| 73 |
+ // RouteAdmitted means the route is able to service requests for the provided Host |
|
| 74 |
+ RouteAdmitted RouteIngressConditionType = "Admitted" |
|
| 75 |
+ // TODO: add other route condition types |
|
| 76 |
+) |
|
| 77 |
+ |
|
| 78 |
+// RouteIngressCondition contains details for the current condition of this pod. |
|
| 79 |
+// TODO: add LastTransitionTime, Reason, Message to match NodeCondition api. |
|
| 80 |
+type RouteIngressCondition struct {
|
|
| 81 |
+ // Type is the type of the condition. |
|
| 82 |
+ // Currently only Ready. |
|
| 83 |
+ Type RouteIngressConditionType |
|
| 84 |
+ // Status is the status of the condition. |
|
| 85 |
+ // Can be True, False, Unknown. |
|
| 86 |
+ Status kapi.ConditionStatus |
|
| 87 |
+ // (brief) reason for the condition's last transition, and is usually a machine and human |
|
| 88 |
+ // readable constant |
|
| 89 |
+ Reason string |
|
| 90 |
+ // Human readable message indicating details about last transition. |
|
| 91 |
+ Message string |
|
| 92 |
+ // RFC 3339 date and time at which the object was acknowledged by the router. |
|
| 93 |
+ // This may be before the router exposes the route |
|
| 94 |
+ LastTransitionTime *unversioned.Time |
|
| 52 | 95 |
} |
| 53 | 96 |
|
| 54 | 97 |
// RouteList is a collection of Routes. |
| ... | ... |
@@ -61,6 +61,48 @@ type RoutePort struct {
|
| 61 | 61 |
// RouteStatus provides relevant info about the status of a route, including which routers |
| 62 | 62 |
// acknowledge it. |
| 63 | 63 |
type RouteStatus struct {
|
| 64 |
+ // Ingress describes the places where the route may be exposed. The list of |
|
| 65 |
+ // ingress points may contain duplicate Host or RouterName values. Routes |
|
| 66 |
+ // are considered live once they are `Ready` |
|
| 67 |
+ Ingress []RouteIngress `json:"ingress" description:"traffic reaches this route via these ingress paths"` |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 70 |
+// RouteIngress holds information about the places where a route is exposed |
|
| 71 |
+type RouteIngress struct {
|
|
| 72 |
+ // Host is the host string under which the route is exposed; this value is required |
|
| 73 |
+ Host string `json:"host,omitempty" description:"the host name this route is exposed to by the specified router"` |
|
| 74 |
+ // Name is a name chosen by the router to identify itself; this value is required |
|
| 75 |
+ RouterName string `json:"routerName,omitempty" description:"the name of the router exposing this route"` |
|
| 76 |
+ // Conditions is the state of the route, may be empty. |
|
| 77 |
+ Conditions []RouteIngressCondition `json:"conditions,omitempty" description:"the conditions that apply to this router" patchStrategy:"merge" patchMergeKey:"type"` |
|
| 78 |
+} |
|
| 79 |
+ |
|
| 80 |
+// RouteIngressConditionType is a valid value for RouteCondition |
|
| 81 |
+type RouteIngressConditionType string |
|
| 82 |
+ |
|
| 83 |
+// These are valid conditions of pod. |
|
| 84 |
+const ( |
|
| 85 |
+ // RouteAdmitted means the route is able to service requests for the provided Host |
|
| 86 |
+ RouteAdmitted RouteIngressConditionType = "Admitted" |
|
| 87 |
+ // TODO: add other route condition types |
|
| 88 |
+) |
|
| 89 |
+ |
|
| 90 |
+// RouteIngressCondition contains details for the current condition of this pod. |
|
| 91 |
+// TODO: add LastTransitionTime, Reason, Message to match NodeCondition api. |
|
| 92 |
+type RouteIngressCondition struct {
|
|
| 93 |
+ // Type is the type of the condition. |
|
| 94 |
+ // Currently only Ready. |
|
| 95 |
+ Type RouteIngressConditionType `json:"type" description:"the type of the condition"` |
|
| 96 |
+ // Status is the status of the condition. |
|
| 97 |
+ // Can be True, False, Unknown. |
|
| 98 |
+ Status kapi.ConditionStatus `json:"status" description:"status is the status of the condition; True, False, or Unknown"` |
|
| 99 |
+ // (brief) reason for the condition's last transition, and is usually a machine and human |
|
| 100 |
+ // readable constant |
|
| 101 |
+ Reason string `json:"reason,omitempty" description:"brief reason for the condition's last transition, machine readable constant"` |
|
| 102 |
+ // Human readable message indicating details about last transition. |
|
| 103 |
+ Message string `json:"message,omitempty" description:"human readable message indicating details about this condition"` |
|
| 104 |
+ // RFC 3339 date and time when this condition last transitioned |
|
| 105 |
+ LastTransitionTime *unversioned.Time `json:"lastTransitionTime,omitempty" description:"the last time at which this condition transitioned to the current status"` |
|
| 64 | 106 |
} |
| 65 | 107 |
|
| 66 | 108 |
// RouterShard has information of a routing shard and is used to |
| ... | ... |
@@ -57,6 +57,49 @@ type RoutePort struct {
|
| 57 | 57 |
// RouteStatus provides relevant info about the status of a route, including which routers |
| 58 | 58 |
// acknowledge it. |
| 59 | 59 |
type RouteStatus struct {
|
| 60 |
+ // Ingress describes the places where the route may be exposed. The list of |
|
| 61 |
+ // ingress points may contain duplicate Host or RouterName values. Routes |
|
| 62 |
+ // are considered live once they are `Ready` |
|
| 63 |
+ Ingress []RouteIngress `json:"ingress"` |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+// RouteIngress holds information about the places where a route is exposed |
|
| 67 |
+type RouteIngress struct {
|
|
| 68 |
+ // Host is the host string under which the route is exposed; this value is required |
|
| 69 |
+ Host string `json:"host,omitempty"` |
|
| 70 |
+ // Name is a name chosen by the router to identify itself; this value is required |
|
| 71 |
+ RouterName string `json:"routerName,omitempty"` |
|
| 72 |
+ // Conditions is the state of the route, may be empty. |
|
| 73 |
+ Conditions []RouteIngressCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` |
|
| 74 |
+} |
|
| 75 |
+ |
|
| 76 |
+// RouteIngressConditionType is a valid value for RouteCondition |
|
| 77 |
+type RouteIngressConditionType string |
|
| 78 |
+ |
|
| 79 |
+// These are valid conditions of pod. |
|
| 80 |
+const ( |
|
| 81 |
+ // RouteAdmitted means the route is able to service requests for the provided Host |
|
| 82 |
+ RouteAdmitted RouteIngressConditionType = "Admitted" |
|
| 83 |
+ // TODO: add other route condition types |
|
| 84 |
+) |
|
| 85 |
+ |
|
| 86 |
+// RouteIngressCondition contains details for the current condition of this pod. |
|
| 87 |
+// TODO: add LastTransitionTime, Reason, Message to match NodeCondition api. |
|
| 88 |
+type RouteIngressCondition struct {
|
|
| 89 |
+ // Type is the type of the condition. |
|
| 90 |
+ // Currently only Ready. |
|
| 91 |
+ Type RouteIngressConditionType `json:"type"` |
|
| 92 |
+ // Status is the status of the condition. |
|
| 93 |
+ // Can be True, False, Unknown. |
|
| 94 |
+ Status kapi.ConditionStatus `json:"status"` |
|
| 95 |
+ // (brief) reason for the condition's last transition, and is usually a machine and human |
|
| 96 |
+ // readable constant |
|
| 97 |
+ Reason string `json:"reason,omitempty"` |
|
| 98 |
+ // Human readable message indicating details about last transition. |
|
| 99 |
+ Message string `json:"message,omitempty"` |
|
| 100 |
+ // RFC 3339 date and time at which the object was acknowledged by the router. |
|
| 101 |
+ // This may be before the router exposes the route |
|
| 102 |
+ LastTransitionTime *unversioned.Time `json:"lastTransitionTime,omitempty"` |
|
| 60 | 103 |
} |
| 61 | 104 |
|
| 62 | 105 |
// RouterShard has information of a routing shard and is used to |
| ... | ... |
@@ -46,7 +46,10 @@ func NewREST(s storage.Interface, allocator route.RouteAllocator) (*REST, *Statu |
| 46 | 46 |
Storage: s, |
| 47 | 47 |
} |
| 48 | 48 |
|
| 49 |
- return &REST{store}, &StatusREST{store}
|
|
| 49 |
+ statusStore := *store |
|
| 50 |
+ statusStore.UpdateStrategy = rest.StatusStrategy |
|
| 51 |
+ |
|
| 52 |
+ return &REST{store}, &StatusREST{&statusStore}
|
|
| 50 | 53 |
} |
| 51 | 54 |
|
| 52 | 55 |
// StatusREST implements the REST endpoint for changing the status of a route. |
| 53 | 56 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,238 @@ |
| 0 |
+package controller |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "time" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/golang/glog" |
|
| 7 |
+ lru "github.com/hashicorp/golang-lru" |
|
| 8 |
+ |
|
| 9 |
+ kapi "k8s.io/kubernetes/pkg/api" |
|
| 10 |
+ "k8s.io/kubernetes/pkg/api/errors" |
|
| 11 |
+ "k8s.io/kubernetes/pkg/api/unversioned" |
|
| 12 |
+ "k8s.io/kubernetes/pkg/util" |
|
| 13 |
+ "k8s.io/kubernetes/pkg/util/sets" |
|
| 14 |
+ "k8s.io/kubernetes/pkg/watch" |
|
| 15 |
+ |
|
| 16 |
+ "github.com/openshift/origin/pkg/client" |
|
| 17 |
+ routeapi "github.com/openshift/origin/pkg/route/api" |
|
| 18 |
+ "github.com/openshift/origin/pkg/router" |
|
| 19 |
+) |
|
| 20 |
+ |
|
| 21 |
+// StatusAdmitter ensures routes added to the plugin have status set. |
|
| 22 |
+type StatusAdmitter struct {
|
|
| 23 |
+ plugin router.Plugin |
|
| 24 |
+ client client.RoutesNamespacer |
|
| 25 |
+ routerName string |
|
| 26 |
+ |
|
| 27 |
+ contentionInterval time.Duration |
|
| 28 |
+ expected *lru.Cache |
|
| 29 |
+} |
|
| 30 |
+ |
|
| 31 |
+// NewStatusAdmitter creates a plugin wrapper that ensures every accepted |
|
| 32 |
+// route has a status field set that matches this router. The admitter manages |
|
| 33 |
+// an LRU of recently seen conflicting updates to handle when two router processes |
|
| 34 |
+// with differing configurations are writing updates at the same time. |
|
| 35 |
+func NewStatusAdmitter(plugin router.Plugin, client client.RoutesNamespacer, name string) *StatusAdmitter {
|
|
| 36 |
+ expected, _ := lru.New(1024) |
|
| 37 |
+ return &StatusAdmitter{
|
|
| 38 |
+ plugin: plugin, |
|
| 39 |
+ client: client, |
|
| 40 |
+ routerName: name, |
|
| 41 |
+ |
|
| 42 |
+ contentionInterval: 1 * time.Minute, |
|
| 43 |
+ expected: expected, |
|
| 44 |
+ } |
|
| 45 |
+} |
|
| 46 |
+ |
|
| 47 |
+// nowFn allows the package to be tested |
|
| 48 |
+var nowFn = unversioned.Now |
|
| 49 |
+ |
|
| 50 |
+// findOrCreateIngress loops through the router status ingress array looking for an entry |
|
| 51 |
+// that matches name. If there is no entry in the array, it creates one and appends it |
|
| 52 |
+// to the array. If there are multiple entries with that name, the first one is |
|
| 53 |
+// returned and later ones are removed. Changed is returned as true if any part of the |
|
| 54 |
+// array is altered. |
|
| 55 |
+func findOrCreateIngress(route *routeapi.Route, name string) (_ *routeapi.RouteIngress, changed bool) {
|
|
| 56 |
+ position := -1 |
|
| 57 |
+ updated := make([]routeapi.RouteIngress, 0, len(route.Status.Ingress)) |
|
| 58 |
+ for i := range route.Status.Ingress {
|
|
| 59 |
+ existing := &route.Status.Ingress[i] |
|
| 60 |
+ if existing.RouterName != name {
|
|
| 61 |
+ updated = append(updated, *existing) |
|
| 62 |
+ continue |
|
| 63 |
+ } |
|
| 64 |
+ if position != -1 {
|
|
| 65 |
+ changed = true |
|
| 66 |
+ continue |
|
| 67 |
+ } |
|
| 68 |
+ updated = append(updated, *existing) |
|
| 69 |
+ position = i |
|
| 70 |
+ } |
|
| 71 |
+ switch {
|
|
| 72 |
+ case position == -1: |
|
| 73 |
+ position = len(route.Status.Ingress) |
|
| 74 |
+ route.Status.Ingress = append(route.Status.Ingress, routeapi.RouteIngress{
|
|
| 75 |
+ RouterName: name, |
|
| 76 |
+ Host: route.Spec.Host, |
|
| 77 |
+ }) |
|
| 78 |
+ changed = true |
|
| 79 |
+ case changed: |
|
| 80 |
+ route.Status.Ingress = updated |
|
| 81 |
+ } |
|
| 82 |
+ ingress := &route.Status.Ingress[position] |
|
| 83 |
+ if ingress.Host != route.Spec.Host {
|
|
| 84 |
+ ingress.Host = route.Spec.Host |
|
| 85 |
+ changed = true |
|
| 86 |
+ } |
|
| 87 |
+ return ingress, changed |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+// setIngressCondition records the condition on the ingress, returning true if the ingress was changed and |
|
| 91 |
+// false if no modification was made. |
|
| 92 |
+func setIngressCondition(ingress *routeapi.RouteIngress, condition routeapi.RouteIngressCondition) bool {
|
|
| 93 |
+ for _, existing := range ingress.Conditions {
|
|
| 94 |
+ //existing.LastTransitionTime = nil |
|
| 95 |
+ if existing == condition {
|
|
| 96 |
+ return false |
|
| 97 |
+ } |
|
| 98 |
+ } |
|
| 99 |
+ now := nowFn() |
|
| 100 |
+ condition.LastTransitionTime = &now |
|
| 101 |
+ ingress.Conditions = []routeapi.RouteIngressCondition{condition}
|
|
| 102 |
+ return true |
|
| 103 |
+} |
|
| 104 |
+ |
|
| 105 |
+func ingressConditionTouched(ingress *routeapi.RouteIngress) *unversioned.Time {
|
|
| 106 |
+ var lastTouch *unversioned.Time |
|
| 107 |
+ for _, condition := range ingress.Conditions {
|
|
| 108 |
+ if t := condition.LastTransitionTime; t != nil {
|
|
| 109 |
+ switch {
|
|
| 110 |
+ case lastTouch == nil, t.After(lastTouch.Time): |
|
| 111 |
+ lastTouch = t |
|
| 112 |
+ } |
|
| 113 |
+ } |
|
| 114 |
+ } |
|
| 115 |
+ return lastTouch |
|
| 116 |
+} |
|
| 117 |
+ |
|
| 118 |
+// recordIngressConditionFailure updates the matching ingress on the route (or adds a new one) with the specified |
|
| 119 |
+// condition, returning true if the object was modified. |
|
| 120 |
+func recordIngressConditionFailure(route *routeapi.Route, name string, condition routeapi.RouteIngressCondition) (*routeapi.RouteIngress, bool, *unversioned.Time) {
|
|
| 121 |
+ for i := range route.Status.Ingress {
|
|
| 122 |
+ existing := &route.Status.Ingress[i] |
|
| 123 |
+ if existing.RouterName != name {
|
|
| 124 |
+ continue |
|
| 125 |
+ } |
|
| 126 |
+ lastTouch := ingressConditionTouched(existing) |
|
| 127 |
+ return existing, setIngressCondition(existing, condition), lastTouch |
|
| 128 |
+ } |
|
| 129 |
+ route.Status.Ingress = append(route.Status.Ingress, routeapi.RouteIngress{RouterName: name})
|
|
| 130 |
+ ingress := &route.Status.Ingress[len(route.Status.Ingress)-1] |
|
| 131 |
+ setIngressCondition(ingress, condition) |
|
| 132 |
+ return ingress, true, nil |
|
| 133 |
+} |
|
| 134 |
+ |
|
| 135 |
+// hasIngressBeenTouched returns true if the route appears to have been touched since the last time |
|
| 136 |
+func (a *StatusAdmitter) hasIngressBeenTouched(route *routeapi.Route, lastTouch *unversioned.Time) bool {
|
|
| 137 |
+ glog.V(4).Infof("has last touch %v for %s/%s", lastTouch, route.Namespace, route.Name)
|
|
| 138 |
+ if lastTouch.IsZero() {
|
|
| 139 |
+ return false |
|
| 140 |
+ } |
|
| 141 |
+ old, ok := a.expected.Get(route.UID) |
|
| 142 |
+ if !ok || old.(time.Time).Equal(lastTouch.Time) {
|
|
| 143 |
+ return false |
|
| 144 |
+ } |
|
| 145 |
+ return true |
|
| 146 |
+} |
|
| 147 |
+ |
|
| 148 |
+// recordIngressTouch |
|
| 149 |
+func (a *StatusAdmitter) recordIngressTouch(route *routeapi.Route, touch *unversioned.Time, err error) {
|
|
| 150 |
+ switch {
|
|
| 151 |
+ case err == nil: |
|
| 152 |
+ if touch != nil {
|
|
| 153 |
+ a.expected.Add(route.UID, touch.Time) |
|
| 154 |
+ } |
|
| 155 |
+ case errors.IsConflict(err): |
|
| 156 |
+ a.expected.Add(route.UID, time.Time{})
|
|
| 157 |
+ } |
|
| 158 |
+} |
|
| 159 |
+ |
|
| 160 |
+// admitRoute returns true if the route has already been accepted to this router, or |
|
| 161 |
+// updates the route to contain an accepted condition. Returns an error if the route could |
|
| 162 |
+// not be admitted. |
|
| 163 |
+func (a *StatusAdmitter) admitRoute(oc client.RoutesNamespacer, route *routeapi.Route, name string) (bool, error) {
|
|
| 164 |
+ ingress, updated := findOrCreateIngress(route, name) |
|
| 165 |
+ if !updated {
|
|
| 166 |
+ for i := range ingress.Conditions {
|
|
| 167 |
+ cond := &ingress.Conditions[i] |
|
| 168 |
+ if cond.Type == routeapi.RouteAdmitted && cond.Status == kapi.ConditionTrue {
|
|
| 169 |
+ glog.V(4).Infof("admit: route already admitted")
|
|
| 170 |
+ return true, nil |
|
| 171 |
+ } |
|
| 172 |
+ } |
|
| 173 |
+ } |
|
| 174 |
+ |
|
| 175 |
+ if a.hasIngressBeenTouched(route, ingressConditionTouched(ingress)) {
|
|
| 176 |
+ glog.V(4).Infof("admit: observed a route update from someone else: route %s/%s has been updated to an inconsistent value, doing nothing", route.Namespace, route.Name)
|
|
| 177 |
+ return true, nil |
|
| 178 |
+ } |
|
| 179 |
+ |
|
| 180 |
+ setIngressCondition(ingress, routeapi.RouteIngressCondition{
|
|
| 181 |
+ Type: routeapi.RouteAdmitted, |
|
| 182 |
+ Status: kapi.ConditionTrue, |
|
| 183 |
+ }) |
|
| 184 |
+ glog.V(4).Infof("admit: admitting route by updating status: %s (%t): %s", route.Name, updated, route.Spec.Host)
|
|
| 185 |
+ _, err := oc.Routes(route.Namespace).UpdateStatus(route) |
|
| 186 |
+ a.recordIngressTouch(route, ingress.Conditions[0].LastTransitionTime, err) |
|
| 187 |
+ return err == nil, err |
|
| 188 |
+} |
|
| 189 |
+ |
|
| 190 |
+// RecordRouteRejection attempts to update the route status with a reason for a route being rejected. |
|
| 191 |
+func (a *StatusAdmitter) RecordRouteRejection(route *routeapi.Route, reason, message string) {
|
|
| 192 |
+ ingress, changed, lastTouch := recordIngressConditionFailure(route, a.routerName, routeapi.RouteIngressCondition{
|
|
| 193 |
+ Type: routeapi.RouteAdmitted, |
|
| 194 |
+ Status: kapi.ConditionFalse, |
|
| 195 |
+ Reason: reason, |
|
| 196 |
+ Message: message, |
|
| 197 |
+ }) |
|
| 198 |
+ if !changed {
|
|
| 199 |
+ glog.V(4).Infof("reject: no changes to route needed: %s/%s", route.Namespace, route.Name)
|
|
| 200 |
+ return |
|
| 201 |
+ } |
|
| 202 |
+ |
|
| 203 |
+ if a.hasIngressBeenTouched(route, lastTouch) {
|
|
| 204 |
+ glog.V(4).Infof("reject: observed a route update from someone else: route %s/%s has been updated to an inconsistent value, doing nothing", route.Namespace, route.Name)
|
|
| 205 |
+ return |
|
| 206 |
+ } |
|
| 207 |
+ |
|
| 208 |
+ _, err := a.client.Routes(route.Namespace).UpdateStatus(route) |
|
| 209 |
+ a.recordIngressTouch(route, ingress.Conditions[0].LastTransitionTime, err) |
|
| 210 |
+ if err != nil {
|
|
| 211 |
+ util.HandleError(fmt.Errorf("unable to write route rejection to the status: %v", err))
|
|
| 212 |
+ } |
|
| 213 |
+} |
|
| 214 |
+ |
|
| 215 |
+// HandleRoute attempts to admit the provided route on watch add / modifications. |
|
| 216 |
+func (a *StatusAdmitter) HandleRoute(eventType watch.EventType, route *routeapi.Route) error {
|
|
| 217 |
+ switch eventType {
|
|
| 218 |
+ case watch.Added, watch.Modified: |
|
| 219 |
+ ok, err := a.admitRoute(a.client, route, a.routerName) |
|
| 220 |
+ if err != nil {
|
|
| 221 |
+ return err |
|
| 222 |
+ } |
|
| 223 |
+ if !ok {
|
|
| 224 |
+ glog.V(4).Infof("skipping route: %s", route.Name)
|
|
| 225 |
+ return nil |
|
| 226 |
+ } |
|
| 227 |
+ } |
|
| 228 |
+ return a.plugin.HandleRoute(eventType, route) |
|
| 229 |
+} |
|
| 230 |
+ |
|
| 231 |
+func (a *StatusAdmitter) HandleEndpoints(eventType watch.EventType, route *kapi.Endpoints) error {
|
|
| 232 |
+ return a.plugin.HandleEndpoints(eventType, route) |
|
| 233 |
+} |
|
| 234 |
+ |
|
| 235 |
+func (a *StatusAdmitter) HandleNamespaces(namespaces sets.String) error {
|
|
| 236 |
+ return a.plugin.HandleNamespaces(namespaces) |
|
| 237 |
+} |
| 0 | 238 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,313 @@ |
| 0 |
+package controller |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "reflect" |
|
| 5 |
+ "testing" |
|
| 6 |
+ "time" |
|
| 7 |
+ |
|
| 8 |
+ kapi "k8s.io/kubernetes/pkg/api" |
|
| 9 |
+ "k8s.io/kubernetes/pkg/api/errors" |
|
| 10 |
+ "k8s.io/kubernetes/pkg/api/unversioned" |
|
| 11 |
+ ktestclient "k8s.io/kubernetes/pkg/client/unversioned/testclient" |
|
| 12 |
+ "k8s.io/kubernetes/pkg/types" |
|
| 13 |
+ "k8s.io/kubernetes/pkg/util/sets" |
|
| 14 |
+ "k8s.io/kubernetes/pkg/watch" |
|
| 15 |
+ |
|
| 16 |
+ "github.com/openshift/origin/pkg/client/testclient" |
|
| 17 |
+ routeapi "github.com/openshift/origin/pkg/route/api" |
|
| 18 |
+) |
|
| 19 |
+ |
|
| 20 |
+type fakePlugin struct {
|
|
| 21 |
+ t watch.EventType |
|
| 22 |
+ route *routeapi.Route |
|
| 23 |
+ err error |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+func (p *fakePlugin) HandleRoute(t watch.EventType, route *routeapi.Route) error {
|
|
| 27 |
+ p.t, p.route = t, route |
|
| 28 |
+ return p.err |
|
| 29 |
+} |
|
| 30 |
+func (p *fakePlugin) HandleEndpoints(watch.EventType, *kapi.Endpoints) error {
|
|
| 31 |
+ return fmt.Errorf("not expected")
|
|
| 32 |
+} |
|
| 33 |
+func (p *fakePlugin) HandleNamespaces(namespaces sets.String) error {
|
|
| 34 |
+ return fmt.Errorf("not expected")
|
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+func TestStatusNoOp(t *testing.T) {
|
|
| 38 |
+ now := unversioned.Now() |
|
| 39 |
+ touched := unversioned.Time{Time: now.Add(-time.Minute)}
|
|
| 40 |
+ p := &fakePlugin{}
|
|
| 41 |
+ c := testclient.NewSimpleFake() |
|
| 42 |
+ admitter := NewStatusAdmitter(p, c, "test") |
|
| 43 |
+ err := admitter.HandleRoute(watch.Added, &routeapi.Route{
|
|
| 44 |
+ ObjectMeta: kapi.ObjectMeta{Name: "route1", Namespace: "default", UID: types.UID("uid1")},
|
|
| 45 |
+ Spec: routeapi.RouteSpec{Host: "route1.test.local"},
|
|
| 46 |
+ Status: routeapi.RouteStatus{
|
|
| 47 |
+ Ingress: []routeapi.RouteIngress{
|
|
| 48 |
+ {
|
|
| 49 |
+ Host: "route1.test.local", |
|
| 50 |
+ RouterName: "test", |
|
| 51 |
+ Conditions: []routeapi.RouteIngressCondition{
|
|
| 52 |
+ {
|
|
| 53 |
+ Type: routeapi.RouteAdmitted, |
|
| 54 |
+ Status: kapi.ConditionTrue, |
|
| 55 |
+ LastTransitionTime: &touched, |
|
| 56 |
+ }, |
|
| 57 |
+ }, |
|
| 58 |
+ }, |
|
| 59 |
+ }, |
|
| 60 |
+ }, |
|
| 61 |
+ }) |
|
| 62 |
+ if err != nil {
|
|
| 63 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 64 |
+ } |
|
| 65 |
+ if len(c.Actions()) > 0 {
|
|
| 66 |
+ t.Fatalf("unexpected actions: %#v", c.Actions())
|
|
| 67 |
+ } |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 70 |
+func TestStatusResetsHost(t *testing.T) {
|
|
| 71 |
+ now := unversioned.Now() |
|
| 72 |
+ nowFn = func() unversioned.Time { return now }
|
|
| 73 |
+ touched := unversioned.Time{Time: now.Add(-time.Minute)}
|
|
| 74 |
+ p := &fakePlugin{}
|
|
| 75 |
+ c := testclient.NewSimpleFake(&routeapi.Route{})
|
|
| 76 |
+ admitter := NewStatusAdmitter(p, c, "test") |
|
| 77 |
+ err := admitter.HandleRoute(watch.Added, &routeapi.Route{
|
|
| 78 |
+ ObjectMeta: kapi.ObjectMeta{Name: "route1", Namespace: "default", UID: types.UID("uid1")},
|
|
| 79 |
+ Spec: routeapi.RouteSpec{Host: "route1.test.local"},
|
|
| 80 |
+ Status: routeapi.RouteStatus{
|
|
| 81 |
+ Ingress: []routeapi.RouteIngress{
|
|
| 82 |
+ {
|
|
| 83 |
+ Host: "route2.test.local", |
|
| 84 |
+ RouterName: "test", |
|
| 85 |
+ Conditions: []routeapi.RouteIngressCondition{
|
|
| 86 |
+ {
|
|
| 87 |
+ Type: routeapi.RouteAdmitted, |
|
| 88 |
+ Status: kapi.ConditionTrue, |
|
| 89 |
+ LastTransitionTime: &touched, |
|
| 90 |
+ }, |
|
| 91 |
+ }, |
|
| 92 |
+ }, |
|
| 93 |
+ }, |
|
| 94 |
+ }, |
|
| 95 |
+ }) |
|
| 96 |
+ if err != nil {
|
|
| 97 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 98 |
+ } |
|
| 99 |
+ if len(c.Actions()) != 1 {
|
|
| 100 |
+ t.Fatalf("unexpected actions: %#v", c.Actions())
|
|
| 101 |
+ } |
|
| 102 |
+ action := c.Actions()[0] |
|
| 103 |
+ if action.GetVerb() != "update" || action.GetResource() != "routes" || action.GetSubresource() != "status" {
|
|
| 104 |
+ t.Fatalf("unexpected action: %#v", action)
|
|
| 105 |
+ } |
|
| 106 |
+ obj := c.Actions()[0].(ktestclient.UpdateAction).GetObject().(*routeapi.Route) |
|
| 107 |
+ if len(obj.Status.Ingress) != 1 && obj.Status.Ingress[0].Host != "route1.test.local" {
|
|
| 108 |
+ t.Fatalf("expected route reset: %#v", obj)
|
|
| 109 |
+ } |
|
| 110 |
+ condition := obj.Status.Ingress[0].Conditions[0] |
|
| 111 |
+ if condition.LastTransitionTime == nil || *condition.LastTransitionTime != now || condition.Status != kapi.ConditionTrue || condition.Reason != "" {
|
|
| 112 |
+ t.Fatalf("unexpected condition: %#v", condition)
|
|
| 113 |
+ } |
|
| 114 |
+ if v, ok := admitter.expected.Peek(types.UID("uid1")); !ok || !reflect.DeepEqual(v, now.Time) {
|
|
| 115 |
+ t.Fatalf("did not record last modification time: %#v %#v", admitter.expected, v)
|
|
| 116 |
+ } |
|
| 117 |
+} |
|
| 118 |
+ |
|
| 119 |
+func TestStatusBackoffOnConflict(t *testing.T) {
|
|
| 120 |
+ now := unversioned.Now() |
|
| 121 |
+ nowFn = func() unversioned.Time { return now }
|
|
| 122 |
+ touched := unversioned.Time{Time: now.Add(-time.Minute)}
|
|
| 123 |
+ p := &fakePlugin{}
|
|
| 124 |
+ c := testclient.NewSimpleFake(&(errors.NewConflict(kapi.Resource("Route"), "route1", nil).(*errors.StatusError).ErrStatus))
|
|
| 125 |
+ admitter := NewStatusAdmitter(p, c, "test") |
|
| 126 |
+ err := admitter.HandleRoute(watch.Added, &routeapi.Route{
|
|
| 127 |
+ ObjectMeta: kapi.ObjectMeta{Name: "route1", Namespace: "default", UID: types.UID("uid1")},
|
|
| 128 |
+ Spec: routeapi.RouteSpec{Host: "route1.test.local"},
|
|
| 129 |
+ Status: routeapi.RouteStatus{
|
|
| 130 |
+ Ingress: []routeapi.RouteIngress{
|
|
| 131 |
+ {
|
|
| 132 |
+ Host: "route2.test.local", |
|
| 133 |
+ RouterName: "test", |
|
| 134 |
+ Conditions: []routeapi.RouteIngressCondition{
|
|
| 135 |
+ {
|
|
| 136 |
+ Type: routeapi.RouteAdmitted, |
|
| 137 |
+ Status: kapi.ConditionFalse, |
|
| 138 |
+ LastTransitionTime: &touched, |
|
| 139 |
+ }, |
|
| 140 |
+ }, |
|
| 141 |
+ }, |
|
| 142 |
+ }, |
|
| 143 |
+ }, |
|
| 144 |
+ }) |
|
| 145 |
+ if len(c.Actions()) != 1 {
|
|
| 146 |
+ t.Fatalf("unexpected actions: %#v", c.Actions())
|
|
| 147 |
+ } |
|
| 148 |
+ action := c.Actions()[0] |
|
| 149 |
+ if action.GetVerb() != "update" || action.GetResource() != "routes" || action.GetSubresource() != "status" {
|
|
| 150 |
+ t.Fatalf("unexpected action: %#v", action)
|
|
| 151 |
+ } |
|
| 152 |
+ obj := c.Actions()[0].(ktestclient.UpdateAction).GetObject().(*routeapi.Route) |
|
| 153 |
+ if len(obj.Status.Ingress) != 1 && obj.Status.Ingress[0].Host != "route1.test.local" {
|
|
| 154 |
+ t.Fatalf("expected route reset: %#v", obj)
|
|
| 155 |
+ } |
|
| 156 |
+ condition := obj.Status.Ingress[0].Conditions[0] |
|
| 157 |
+ if condition.LastTransitionTime == nil || *condition.LastTransitionTime != now || condition.Status != kapi.ConditionTrue || condition.Reason != "" {
|
|
| 158 |
+ t.Fatalf("unexpected condition: %#v", condition)
|
|
| 159 |
+ } |
|
| 160 |
+ |
|
| 161 |
+ if err == nil {
|
|
| 162 |
+ t.Fatalf("unexpected non-error: %#v", admitter.expected)
|
|
| 163 |
+ } |
|
| 164 |
+ if v, ok := admitter.expected.Peek(types.UID("uid1")); !ok || !reflect.DeepEqual(v, time.Time{}) {
|
|
| 165 |
+ t.Fatalf("expected empty time: %#v", v)
|
|
| 166 |
+ } |
|
| 167 |
+} |
|
| 168 |
+ |
|
| 169 |
+func TestStatusRecordRejection(t *testing.T) {
|
|
| 170 |
+ now := unversioned.Now() |
|
| 171 |
+ nowFn = func() unversioned.Time { return now }
|
|
| 172 |
+ touched := unversioned.Time{Time: now.Add(-time.Minute)}
|
|
| 173 |
+ p := &fakePlugin{}
|
|
| 174 |
+ c := testclient.NewSimpleFake(&routeapi.Route{})
|
|
| 175 |
+ admitter := NewStatusAdmitter(p, c, "test") |
|
| 176 |
+ admitter.RecordRouteRejection(&routeapi.Route{
|
|
| 177 |
+ ObjectMeta: kapi.ObjectMeta{Name: "route1", Namespace: "default", UID: types.UID("uid1")},
|
|
| 178 |
+ Spec: routeapi.RouteSpec{Host: "route1.test.local"},
|
|
| 179 |
+ Status: routeapi.RouteStatus{
|
|
| 180 |
+ Ingress: []routeapi.RouteIngress{
|
|
| 181 |
+ {
|
|
| 182 |
+ Host: "route2.test.local", |
|
| 183 |
+ RouterName: "test", |
|
| 184 |
+ Conditions: []routeapi.RouteIngressCondition{
|
|
| 185 |
+ {
|
|
| 186 |
+ Type: routeapi.RouteAdmitted, |
|
| 187 |
+ Status: kapi.ConditionFalse, |
|
| 188 |
+ LastTransitionTime: &touched, |
|
| 189 |
+ }, |
|
| 190 |
+ }, |
|
| 191 |
+ }, |
|
| 192 |
+ }, |
|
| 193 |
+ }, |
|
| 194 |
+ }, "Failed", "generic error") |
|
| 195 |
+ |
|
| 196 |
+ if len(c.Actions()) != 1 {
|
|
| 197 |
+ t.Fatalf("unexpected actions: %#v", c.Actions())
|
|
| 198 |
+ } |
|
| 199 |
+ action := c.Actions()[0] |
|
| 200 |
+ if action.GetVerb() != "update" || action.GetResource() != "routes" || action.GetSubresource() != "status" {
|
|
| 201 |
+ t.Fatalf("unexpected action: %#v", action)
|
|
| 202 |
+ } |
|
| 203 |
+ obj := c.Actions()[0].(ktestclient.UpdateAction).GetObject().(*routeapi.Route) |
|
| 204 |
+ if len(obj.Status.Ingress) != 1 && obj.Status.Ingress[0].Host != "route1.test.local" {
|
|
| 205 |
+ t.Fatalf("expected route reset: %#v", obj)
|
|
| 206 |
+ } |
|
| 207 |
+ condition := obj.Status.Ingress[0].Conditions[0] |
|
| 208 |
+ if condition.LastTransitionTime == nil || *condition.LastTransitionTime != now || condition.Status != kapi.ConditionFalse || condition.Reason != "Failed" || condition.Message != "generic error" {
|
|
| 209 |
+ t.Fatalf("unexpected condition: %#v", condition)
|
|
| 210 |
+ } |
|
| 211 |
+ if v, ok := admitter.expected.Peek(types.UID("uid1")); !ok || !reflect.DeepEqual(v, now.Time) {
|
|
| 212 |
+ t.Fatalf("expected empty time: %#v", v)
|
|
| 213 |
+ } |
|
| 214 |
+} |
|
| 215 |
+ |
|
| 216 |
+func TestStatusRecordRejectionConflict(t *testing.T) {
|
|
| 217 |
+ now := unversioned.Now() |
|
| 218 |
+ nowFn = func() unversioned.Time { return now }
|
|
| 219 |
+ touched := unversioned.Time{Time: now.Add(-time.Minute)}
|
|
| 220 |
+ p := &fakePlugin{}
|
|
| 221 |
+ c := testclient.NewSimpleFake(&(errors.NewConflict(kapi.Resource("Route"), "route1", nil).(*errors.StatusError).ErrStatus))
|
|
| 222 |
+ admitter := NewStatusAdmitter(p, c, "test") |
|
| 223 |
+ admitter.RecordRouteRejection(&routeapi.Route{
|
|
| 224 |
+ ObjectMeta: kapi.ObjectMeta{Name: "route1", Namespace: "default", UID: types.UID("uid1")},
|
|
| 225 |
+ Spec: routeapi.RouteSpec{Host: "route1.test.local"},
|
|
| 226 |
+ Status: routeapi.RouteStatus{
|
|
| 227 |
+ Ingress: []routeapi.RouteIngress{
|
|
| 228 |
+ {
|
|
| 229 |
+ Host: "route2.test.local", |
|
| 230 |
+ RouterName: "test", |
|
| 231 |
+ Conditions: []routeapi.RouteIngressCondition{
|
|
| 232 |
+ {
|
|
| 233 |
+ Type: routeapi.RouteAdmitted, |
|
| 234 |
+ Status: kapi.ConditionFalse, |
|
| 235 |
+ LastTransitionTime: &touched, |
|
| 236 |
+ }, |
|
| 237 |
+ }, |
|
| 238 |
+ }, |
|
| 239 |
+ }, |
|
| 240 |
+ }, |
|
| 241 |
+ }, "Failed", "generic error") |
|
| 242 |
+ |
|
| 243 |
+ if len(c.Actions()) != 1 {
|
|
| 244 |
+ t.Fatalf("unexpected actions: %#v", c.Actions())
|
|
| 245 |
+ } |
|
| 246 |
+ action := c.Actions()[0] |
|
| 247 |
+ if action.GetVerb() != "update" || action.GetResource() != "routes" || action.GetSubresource() != "status" {
|
|
| 248 |
+ t.Fatalf("unexpected action: %#v", action)
|
|
| 249 |
+ } |
|
| 250 |
+ obj := c.Actions()[0].(ktestclient.UpdateAction).GetObject().(*routeapi.Route) |
|
| 251 |
+ if len(obj.Status.Ingress) != 1 && obj.Status.Ingress[0].Host != "route1.test.local" {
|
|
| 252 |
+ t.Fatalf("expected route reset: %#v", obj)
|
|
| 253 |
+ } |
|
| 254 |
+ condition := obj.Status.Ingress[0].Conditions[0] |
|
| 255 |
+ if condition.LastTransitionTime == nil || *condition.LastTransitionTime != now || condition.Status != kapi.ConditionFalse || condition.Reason != "Failed" || condition.Message != "generic error" {
|
|
| 256 |
+ t.Fatalf("unexpected condition: %#v", condition)
|
|
| 257 |
+ } |
|
| 258 |
+ if v, ok := admitter.expected.Peek(types.UID("uid1")); !ok || !reflect.DeepEqual(v, time.Time{}) {
|
|
| 259 |
+ t.Fatalf("expected empty time: %#v", v)
|
|
| 260 |
+ } |
|
| 261 |
+} |
|
| 262 |
+ |
|
| 263 |
+func TestFindOrCreateIngress(t *testing.T) {
|
|
| 264 |
+ route := &routeapi.Route{
|
|
| 265 |
+ Status: routeapi.RouteStatus{
|
|
| 266 |
+ Ingress: []routeapi.RouteIngress{
|
|
| 267 |
+ {
|
|
| 268 |
+ RouterName: "bar", |
|
| 269 |
+ Conditions: []routeapi.RouteIngressCondition{
|
|
| 270 |
+ {
|
|
| 271 |
+ Reason: "bar", |
|
| 272 |
+ }, |
|
| 273 |
+ }, |
|
| 274 |
+ }, |
|
| 275 |
+ {
|
|
| 276 |
+ RouterName: "foo", |
|
| 277 |
+ Conditions: []routeapi.RouteIngressCondition{
|
|
| 278 |
+ {
|
|
| 279 |
+ Reason: "foo1", |
|
| 280 |
+ }, |
|
| 281 |
+ }, |
|
| 282 |
+ }, |
|
| 283 |
+ {
|
|
| 284 |
+ RouterName: "baz", |
|
| 285 |
+ Conditions: []routeapi.RouteIngressCondition{
|
|
| 286 |
+ {
|
|
| 287 |
+ Reason: "baz", |
|
| 288 |
+ }, |
|
| 289 |
+ }, |
|
| 290 |
+ }, |
|
| 291 |
+ {
|
|
| 292 |
+ RouterName: "foo", |
|
| 293 |
+ Conditions: []routeapi.RouteIngressCondition{
|
|
| 294 |
+ {
|
|
| 295 |
+ Reason: "foo2", |
|
| 296 |
+ }, |
|
| 297 |
+ }, |
|
| 298 |
+ }, |
|
| 299 |
+ }, |
|
| 300 |
+ }, |
|
| 301 |
+ } |
|
| 302 |
+ |
|
| 303 |
+ routerName := "foo" |
|
| 304 |
+ ingress, changed := findOrCreateIngress(route, routerName) |
|
| 305 |
+ if !changed {
|
|
| 306 |
+ t.Errorf("expected the route list to be changed: %#v", route.Status.Ingress)
|
|
| 307 |
+ } |
|
| 308 |
+ if ingress.RouterName != routerName {
|
|
| 309 |
+ t.Errorf("returned ingress had router name %s but expected %s", ingress.RouterName, routerName)
|
|
| 310 |
+ } |
|
| 311 |
+ t.Logf("routes: %#v", route.Status.Ingress)
|
|
| 312 |
+} |
| ... | ... |
@@ -23,12 +23,27 @@ func HostForRoute(route *routeapi.Route) string {
|
| 23 | 23 |
type HostToRouteMap map[string][]*routeapi.Route |
| 24 | 24 |
type RouteToHostMap map[string]string |
| 25 | 25 |
|
| 26 |
+// RejectionRecorder is an object capable of recording why a route was rejected |
|
| 27 |
+type RejectionRecorder interface {
|
|
| 28 |
+ RecordRouteRejection(route *routeapi.Route, reason, message string) |
|
| 29 |
+} |
|
| 30 |
+ |
|
| 31 |
+var LogRejections = logRecorder{}
|
|
| 32 |
+ |
|
| 33 |
+type logRecorder struct{}
|
|
| 34 |
+ |
|
| 35 |
+func (_ logRecorder) RecordRouteRejection(route *routeapi.Route, reason, message string) {
|
|
| 36 |
+ glog.V(4).Infof("Rejected route %s: %s: %s", route.Name, reason, message)
|
|
| 37 |
+} |
|
| 38 |
+ |
|
| 26 | 39 |
// UniqueHost implements the router.Plugin interface to provide |
| 27 | 40 |
// a template based, backend-agnostic router. |
| 28 | 41 |
type UniqueHost struct {
|
| 29 | 42 |
plugin router.Plugin |
| 30 | 43 |
hostForRoute RouteHostFunc |
| 31 | 44 |
|
| 45 |
+ recorder RejectionRecorder |
|
| 46 |
+ |
|
| 32 | 47 |
hostToRoute HostToRouteMap |
| 33 | 48 |
routeToHost RouteToHostMap |
| 34 | 49 |
// nil means different than empty |
| ... | ... |
@@ -36,12 +51,15 @@ type UniqueHost struct {
|
| 36 | 36 |
} |
| 37 | 37 |
|
| 38 | 38 |
// NewUniqueHost creates a plugin wrapper that ensures only unique routes are passed into |
| 39 |
-// the underlying plugin. |
|
| 40 |
-func NewUniqueHost(plugin router.Plugin, fn RouteHostFunc) *UniqueHost {
|
|
| 39 |
+// the underlying plugin. Recorder is an interface for indicating why a route was |
|
| 40 |
+// rejected. |
|
| 41 |
+func NewUniqueHost(plugin router.Plugin, fn RouteHostFunc, recorder RejectionRecorder) *UniqueHost {
|
|
| 41 | 42 |
return &UniqueHost{
|
| 42 | 43 |
plugin: plugin, |
| 43 | 44 |
hostForRoute: fn, |
| 44 | 45 |
|
| 46 |
+ recorder: recorder, |
|
| 47 |
+ |
|
| 45 | 48 |
hostToRoute: make(HostToRouteMap), |
| 46 | 49 |
routeToHost: make(RouteToHostMap), |
| 47 | 50 |
} |
| ... | ... |
@@ -81,6 +99,7 @@ func (p *UniqueHost) HandleRoute(eventType watch.EventType, route *routeapi.Rout |
| 81 | 81 |
host := p.hostForRoute(route) |
| 82 | 82 |
if len(host) == 0 {
|
| 83 | 83 |
glog.V(4).Infof("Route %s has no host value", routeName)
|
| 84 |
+ p.recorder.RecordRouteRejection(route, "NoHostValue", "no host value was defined for the route") |
|
| 84 | 85 |
return nil |
| 85 | 86 |
} |
| 86 | 87 |
route.Spec.Host = host |
| ... | ... |
@@ -97,14 +116,17 @@ func (p *UniqueHost) HandleRoute(eventType watch.EventType, route *routeapi.Rout |
| 97 | 97 |
if old[i].Spec.Path == route.Spec.Path {
|
| 98 | 98 |
if old[i].CreationTimestamp.Before(route.CreationTimestamp) {
|
| 99 | 99 |
glog.V(4).Infof("Route %s cannot take %s from %s", routeName, host, routeNameKey(oldest))
|
| 100 |
- return fmt.Errorf("route %s holds %s and is older than %s", routeNameKey(oldest), host, key)
|
|
| 100 |
+ err := fmt.Errorf("route %s already exposes %s and is older", oldest.Name, host)
|
|
| 101 |
+ p.recorder.RecordRouteRejection(route, "HostAlreadyClaimed", err.Error()) |
|
| 102 |
+ return err |
|
| 101 | 103 |
} |
| 102 | 104 |
added = true |
| 103 | 105 |
if old[i].Namespace == route.Namespace && old[i].Name == route.Name {
|
| 104 | 106 |
old[i] = route |
| 105 | 107 |
break |
| 106 | 108 |
} |
| 107 |
- glog.V(4).Infof("Route %s will replace path %s from %s because it is older", routeName, route.Spec.Path, routeNameKey(old[i]))
|
|
| 109 |
+ glog.V(4).Infof("route %s will replace path %s from %s because it is older", routeName, route.Spec.Path, old[i].Name)
|
|
| 110 |
+ p.recorder.RecordRouteRejection(old[i], "HostAlreadyClaimed", fmt.Sprintf("replaced by older route %s", route.Name))
|
|
| 108 | 111 |
p.plugin.HandleRoute(watch.Deleted, old[i]) |
| 109 | 112 |
old[i] = route |
| 110 | 113 |
} |
| ... | ... |
@@ -119,11 +141,14 @@ func (p *UniqueHost) HandleRoute(eventType watch.EventType, route *routeapi.Rout |
| 119 | 119 |
} else {
|
| 120 | 120 |
if oldest.CreationTimestamp.Before(route.CreationTimestamp) {
|
| 121 | 121 |
glog.V(4).Infof("Route %s cannot take %s from %s", routeName, host, routeNameKey(oldest))
|
| 122 |
- return fmt.Errorf("route %s holds %s and is older than %s", routeNameKey(oldest), host, key)
|
|
| 122 |
+ err := fmt.Errorf("another route holds %s and is older than %s", host, route.Name)
|
|
| 123 |
+ p.recorder.RecordRouteRejection(route, "HostAlreadyClaimed", err.Error()) |
|
| 124 |
+ return err |
|
| 123 | 125 |
} |
| 124 | 126 |
|
| 125 | 127 |
glog.V(4).Infof("Route %s is reclaiming %s from namespace %s", routeName, host, oldest.Namespace)
|
| 126 | 128 |
for i := range old {
|
| 129 |
+ p.recorder.RecordRouteRejection(old[i], "HostAlreadyClaimed", fmt.Sprintf("namespace %s owns hostname %s", oldest.Namespace, host))
|
|
| 127 | 130 |
p.plugin.HandleRoute(watch.Deleted, old[i]) |
| 128 | 131 |
} |
| 129 | 132 |
p.hostToRoute[host] = []*routeapi.Route{route}
|
| ... | ... |
@@ -204,7 +204,7 @@ func TestHandleEndpoints(t *testing.T) {
|
| 204 | 204 |
templatePlugin := newDefaultTemplatePlugin(router, true) |
| 205 | 205 |
// TODO: move tests that rely on unique hosts to pkg/router/controller and remove them from |
| 206 | 206 |
// here |
| 207 |
- plugin := controller.NewUniqueHost(templatePlugin, controller.HostForRoute) |
|
| 207 |
+ plugin := controller.NewUniqueHost(templatePlugin, controller.HostForRoute, controller.LogRejections) |
|
| 208 | 208 |
|
| 209 | 209 |
for _, tc := range testCases {
|
| 210 | 210 |
plugin.HandleEndpoints(tc.eventType, tc.endpoints) |
| ... | ... |
@@ -315,7 +315,7 @@ func TestHandleTCPEndpoints(t *testing.T) {
|
| 315 | 315 |
templatePlugin := newDefaultTemplatePlugin(router, false) |
| 316 | 316 |
// TODO: move tests that rely on unique hosts to pkg/router/controller and remove them from |
| 317 | 317 |
// here |
| 318 |
- plugin := controller.NewUniqueHost(templatePlugin, controller.HostForRoute) |
|
| 318 |
+ plugin := controller.NewUniqueHost(templatePlugin, controller.HostForRoute, controller.LogRejections) |
|
| 319 | 319 |
|
| 320 | 320 |
for _, tc := range testCases {
|
| 321 | 321 |
plugin.HandleEndpoints(tc.eventType, tc.endpoints) |
| ... | ... |
@@ -340,13 +340,28 @@ func TestHandleTCPEndpoints(t *testing.T) {
|
| 340 | 340 |
} |
| 341 | 341 |
} |
| 342 | 342 |
|
| 343 |
+type rejection struct {
|
|
| 344 |
+ route *routeapi.Route |
|
| 345 |
+ reason string |
|
| 346 |
+ message string |
|
| 347 |
+} |
|
| 348 |
+ |
|
| 349 |
+type fakeRejections struct {
|
|
| 350 |
+ rejections []rejection |
|
| 351 |
+} |
|
| 352 |
+ |
|
| 353 |
+func (r *fakeRejections) RecordRouteRejection(route *routeapi.Route, reason, message string) {
|
|
| 354 |
+ r.rejections = append(r.rejections, rejection{route: route, reason: reason, message: message})
|
|
| 355 |
+} |
|
| 356 |
+ |
|
| 343 | 357 |
// TestHandleRoute test route watch events |
| 344 | 358 |
func TestHandleRoute(t *testing.T) {
|
| 359 |
+ rejections := &fakeRejections{}
|
|
| 345 | 360 |
router := newTestRouter(make(map[string]ServiceUnit)) |
| 346 | 361 |
templatePlugin := newDefaultTemplatePlugin(router, true) |
| 347 | 362 |
// TODO: move tests that rely on unique hosts to pkg/router/controller and remove them from |
| 348 | 363 |
// here |
| 349 |
- plugin := controller.NewUniqueHost(templatePlugin, controller.HostForRoute) |
|
| 364 |
+ plugin := controller.NewUniqueHost(templatePlugin, controller.HostForRoute, rejections) |
|
| 350 | 365 |
|
| 351 | 366 |
original := unversioned.Time{Time: time.Now()}
|
| 352 | 367 |
|
| ... | ... |
@@ -388,6 +403,10 @@ func TestHandleRoute(t *testing.T) {
|
| 388 | 388 |
} |
| 389 | 389 |
} |
| 390 | 390 |
|
| 391 |
+ if len(rejections.rejections) > 0 {
|
|
| 392 |
+ t.Fatalf("did not expect a recorded rejection: %#v", rejections)
|
|
| 393 |
+ } |
|
| 394 |
+ |
|
| 391 | 395 |
// attempt to add a second route with a newer time, verify it is ignored |
| 392 | 396 |
duplicateRoute := &routeapi.Route{
|
| 393 | 397 |
ObjectMeta: kapi.ObjectMeta{
|
| ... | ... |
@@ -411,6 +430,13 @@ func TestHandleRoute(t *testing.T) {
|
| 411 | 411 |
if r, ok := plugin.RoutesForHost("www.example.com"); !ok || r[0].Name != "test" {
|
| 412 | 412 |
t.Fatalf("unexpected claimed routes: %#v", r)
|
| 413 | 413 |
} |
| 414 |
+ if len(rejections.rejections) != 1 || |
|
| 415 |
+ rejections.rejections[0].route.Name != "dupe" || |
|
| 416 |
+ rejections.rejections[0].reason != "HostAlreadyClaimed" || |
|
| 417 |
+ rejections.rejections[0].message != "route test already exposes www.example.com and is older" {
|
|
| 418 |
+ t.Fatalf("did not record rejection: %#v", rejections)
|
|
| 419 |
+ } |
|
| 420 |
+ rejections.rejections = nil |
|
| 414 | 421 |
|
| 415 | 422 |
// attempt to remove the second route that is not being used, verify it is ignored |
| 416 | 423 |
if err := plugin.HandleRoute(watch.Deleted, duplicateRoute); err == nil {
|
| ... | ... |
@@ -425,6 +451,13 @@ func TestHandleRoute(t *testing.T) {
|
| 425 | 425 |
if r, ok := plugin.RoutesForHost("www.example.com"); !ok || r[0].Name != "test" {
|
| 426 | 426 |
t.Fatalf("unexpected claimed routes: %#v", r)
|
| 427 | 427 |
} |
| 428 |
+ if len(rejections.rejections) != 1 || |
|
| 429 |
+ rejections.rejections[0].route.Name != "dupe" || |
|
| 430 |
+ rejections.rejections[0].reason != "HostAlreadyClaimed" || |
|
| 431 |
+ rejections.rejections[0].message != "route test already exposes www.example.com and is older" {
|
|
| 432 |
+ t.Fatalf("did not record rejection: %#v", rejections)
|
|
| 433 |
+ } |
|
| 434 |
+ rejections.rejections = nil |
|
| 428 | 435 |
|
| 429 | 436 |
// add a second route with an older time, verify it takes effect |
| 430 | 437 |
duplicateRoute.CreationTimestamp = unversioned.Time{Time: original.Add(-time.Hour)}
|
| ... | ... |
@@ -441,6 +474,13 @@ func TestHandleRoute(t *testing.T) {
|
| 441 | 441 |
if _, ok := actualSU.ServiceAliasConfigs[router.routeKey(route)]; ok {
|
| 442 | 442 |
t.Errorf("unexpected service alias config %s", router.routeKey(route))
|
| 443 | 443 |
} |
| 444 |
+ if len(rejections.rejections) != 1 || |
|
| 445 |
+ rejections.rejections[0].route.Name != "test" || |
|
| 446 |
+ rejections.rejections[0].reason != "HostAlreadyClaimed" || |
|
| 447 |
+ rejections.rejections[0].message != "replaced by older route dupe" {
|
|
| 448 |
+ t.Fatalf("did not record rejection: %#v", rejections)
|
|
| 449 |
+ } |
|
| 450 |
+ rejections.rejections = nil |
|
| 444 | 451 |
|
| 445 | 452 |
//mod |
| 446 | 453 |
route.Spec.Host = "www.example2.com" |
| ... | ... |
@@ -467,6 +507,9 @@ func TestHandleRoute(t *testing.T) {
|
| 467 | 467 |
if plugin.HostLen() != 1 {
|
| 468 | 468 |
t.Fatalf("did not clear claimed route: %#v", plugin)
|
| 469 | 469 |
} |
| 470 |
+ if len(rejections.rejections) != 0 {
|
|
| 471 |
+ t.Fatalf("unexpected rejection: %#v", rejections)
|
|
| 472 |
+ } |
|
| 470 | 473 |
|
| 471 | 474 |
//delete |
| 472 | 475 |
if err := plugin.HandleRoute(watch.Deleted, route); err != nil {
|
| ... | ... |
@@ -488,6 +531,9 @@ func TestHandleRoute(t *testing.T) {
|
| 488 | 488 |
if plugin.HostLen() != 0 {
|
| 489 | 489 |
t.Errorf("did not clear claimed route: %#v", plugin)
|
| 490 | 490 |
} |
| 491 |
+ if len(rejections.rejections) != 0 {
|
|
| 492 |
+ t.Fatalf("unexpected rejection: %#v", rejections)
|
|
| 493 |
+ } |
|
| 491 | 494 |
} |
| 492 | 495 |
|
| 493 | 496 |
func TestNamespaceScopingFromEmpty(t *testing.T) {
|
| ... | ... |
@@ -495,7 +541,7 @@ func TestNamespaceScopingFromEmpty(t *testing.T) {
|
| 495 | 495 |
templatePlugin := newDefaultTemplatePlugin(router, true) |
| 496 | 496 |
// TODO: move tests that rely on unique hosts to pkg/router/controller and remove them from |
| 497 | 497 |
// here |
| 498 |
- plugin := controller.NewUniqueHost(templatePlugin, controller.HostForRoute) |
|
| 498 |
+ plugin := controller.NewUniqueHost(templatePlugin, controller.HostForRoute, controller.LogRejections) |
|
| 499 | 499 |
|
| 500 | 500 |
// no namespaces allowed |
| 501 | 501 |
plugin.HandleNamespaces(sets.String{})
|
| ... | ... |
@@ -256,6 +256,12 @@ items: |
| 256 | 256 |
verbs: |
| 257 | 257 |
- get |
| 258 | 258 |
- update |
| 259 |
+ - apiGroups: null |
|
| 260 |
+ attributeRestrictions: null |
|
| 261 |
+ resources: |
|
| 262 |
+ - routes/status |
|
| 263 |
+ verbs: |
|
| 264 |
+ - update |
|
| 259 | 265 |
- apiVersion: v1 |
| 260 | 266 |
kind: ClusterRole |
| 261 | 267 |
metadata: |
| ... | ... |
@@ -671,6 +677,12 @@ items: |
| 671 | 671 |
verbs: |
| 672 | 672 |
- list |
| 673 | 673 |
- watch |
| 674 |
+ - apiGroups: null |
|
| 675 |
+ attributeRestrictions: null |
|
| 676 |
+ resources: |
|
| 677 |
+ - routes/status |
|
| 678 |
+ verbs: |
|
| 679 |
+ - update |
|
| 674 | 680 |
- apiVersion: v1 |
| 675 | 681 |
kind: ClusterRole |
| 676 | 682 |
metadata: |
| ... | ... |
@@ -126,6 +126,11 @@ func (s *TestHttpService) handleRouteList(w http.ResponseWriter, r *http.Request |
| 126 | 126 |
fmt.Fprint(w, "{}")
|
| 127 | 127 |
} |
| 128 | 128 |
|
| 129 |
+// handleRouteCalls handles calls to /osapi/v1/routes/* and returns whatever the client sent |
|
| 130 |
+func (s *TestHttpService) handleRouteCalls(w http.ResponseWriter, r *http.Request) {
|
|
| 131 |
+ fmt.Fprint(w, "{}")
|
|
| 132 |
+} |
|
| 133 |
+ |
|
| 129 | 134 |
// handleEndpointWatch handles calls to /api/v1beta1/watch/endpoints and uses the endpoint channel to simulate watch events |
| 130 | 135 |
func (s *TestHttpService) handleEndpointWatch(w http.ResponseWriter, r *http.Request) {
|
| 131 | 136 |
io.WriteString(w, <-s.EndpointChannel) |
| ... | ... |
@@ -182,6 +187,7 @@ func (s *TestHttpService) startMaster() error {
|
| 182 | 182 |
masterServer.HandleFunc(fmt.Sprintf("/api/%s/endpoints", version), s.handleEndpointList)
|
| 183 | 183 |
masterServer.HandleFunc(fmt.Sprintf("/api/%s/watch/endpoints", version), s.handleEndpointWatch)
|
| 184 | 184 |
masterServer.HandleFunc(fmt.Sprintf("/oapi/%s/routes", version), s.handleRouteList)
|
| 185 |
+ masterServer.HandleFunc(fmt.Sprintf("/oapi/%s/namespaces/", version), s.handleRouteCalls)
|
|
| 185 | 186 |
masterServer.HandleFunc(fmt.Sprintf("/oapi/%s/watch/routes", version), s.handleRouteWatch)
|
| 186 | 187 |
} |
| 187 | 188 |
|