Skip to content

Commit

Permalink
Add status subresource to Ingress
Browse files Browse the repository at this point in the history
  • Loading branch information
derekwaynecarr committed Oct 14, 2015
1 parent a5fc5fd commit eae56c3
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 13 deletions.
21 changes: 21 additions & 0 deletions pkg/api/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -1994,3 +1994,24 @@ func ValidatePodLogOptions(opts *api.PodLogOptions) errs.ValidationErrorList {
}
return allErrs
}

// ValidateLoadBalancerStatus validates required fields on a LoadBalancerStatus
func ValidateLoadBalancerStatus(status *api.LoadBalancerStatus) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
for _, ingress := range status.Ingress {
if len(ingress.IP) > 0 {
if isIP := (net.ParseIP(ingress.IP) != nil); !isIP {
allErrs = append(allErrs, errs.NewFieldInvalid("ingress.ip", ingress.IP, "must be an IP address"))
}
}
if len(ingress.Hostname) > 0 {
if valid, errMsg := NameIsDNSSubdomain(ingress.Hostname, false); !valid {
allErrs = append(allErrs, errs.NewFieldInvalid("ingress.hostname", ingress.Hostname, errMsg))
}
if isIP := (net.ParseIP(ingress.Hostname) != nil); isIP {
allErrs = append(allErrs, errs.NewFieldInvalid("ingress.hostname", ingress.Hostname, "must be a DNS name, not ip address"))
}
}
}
return allErrs
}
8 changes: 8 additions & 0 deletions pkg/apis/extensions/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,14 @@ func ValidateIngressUpdate(oldIngress, ingress *extensions.Ingress) errs.Validat
return allErrs
}

// ValidateIngressStatusUpdate tests if required fields in the Ingress are set when updating status.
func ValidateIngressStatusUpdate(ingress, oldIngress *extensions.Ingress) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta).Prefix("metadata")...)
allErrs = append(allErrs, apivalidation.ValidateLoadBalancerStatus(&ingress.Status.LoadBalancer).Prefix("status.loadBalancer")...)
return allErrs
}

func validateIngressRules(IngressRules []extensions.IngressRule) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
if len(IngressRules) == 0 {
Expand Down
92 changes: 92 additions & 0 deletions pkg/apis/extensions/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,98 @@ func TestValidateIngress(t *testing.T) {
}
}

func TestValidateIngressStatusUpdate(t *testing.T) {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: util.IntOrString{Kind: util.IntstrInt, IntVal: 80},
}

newValid := func() extensions.Ingress {
return extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
ResourceVersion: "9",
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: util.IntOrString{Kind: util.IntstrInt, IntVal: 80},
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
Status: extensions.IngressStatus{
LoadBalancer: api.LoadBalancerStatus{
Ingress: []api.LoadBalancerIngress{
{IP: "127.0.0.1", Hostname: "foo.bar.com"},
},
},
},
}
}
oldValue := newValid()
newValue := newValid()
newValue.Status = extensions.IngressStatus{
LoadBalancer: api.LoadBalancerStatus{
Ingress: []api.LoadBalancerIngress{
{IP: "127.0.0.2", Hostname: "foo.com"},
},
},
}
invalidIP := newValid()
invalidIP.Status = extensions.IngressStatus{
LoadBalancer: api.LoadBalancerStatus{
Ingress: []api.LoadBalancerIngress{
{IP: "abcd", Hostname: "foo.com"},
},
},
}
invalidHostname := newValid()
invalidHostname.Status = extensions.IngressStatus{
LoadBalancer: api.LoadBalancerStatus{
Ingress: []api.LoadBalancerIngress{
{IP: "127.0.0.1", Hostname: "127.0.0.1"},
},
},
}

errs := ValidateIngressStatusUpdate(&newValue, &oldValue)
if len(errs) != 0 {
t.Errorf("Unexpected error %v", errs)
}

errorCases := map[string]extensions.Ingress{
"status.loadBalancer.ingress.ip: invalid value": invalidIP,
"status.loadBalancer.ingress.hostname: invalid value": invalidHostname,
}
for k, v := range errorCases {
errs := ValidateIngressStatusUpdate(&v, &oldValue)
if len(errs) == 0 {
t.Errorf("expected failure for %s", k)
} else {
s := strings.Split(k, ":")
err := errs[0].(*errors.ValidationError)
if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
t.Errorf("unexpected error: %v, expected: %s", errs[0], k)
}
}
}
}

func TestValidateClusterAutoscaler(t *testing.T) {
successCases := []extensions.ClusterAutoscaler{
{
Expand Down
3 changes: 2 additions & 1 deletion pkg/master/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -1032,7 +1032,7 @@ func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion {
daemonSetStorage, daemonSetStatusStorage := daemonetcd.NewREST(dbClient("daemonsets"))
deploymentStorage := deploymentetcd.NewStorage(dbClient("deployments"))
jobStorage, jobStatusStorage := jobetcd.NewREST(dbClient("jobs"))
ingressStorage := ingressetcd.NewREST(dbClient("ingress"))
ingressStorage, ingressStatusStorage := ingressetcd.NewREST(dbClient("ingress"))

thirdPartyControl := ThirdPartyController{
master: m,
Expand All @@ -1058,6 +1058,7 @@ func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion {
strings.ToLower("jobs"): jobStorage,
strings.ToLower("jobs/status"): jobStatusStorage,
strings.ToLower("ingress"): ingressStorage,
strings.ToLower("ingress/status"): ingressStatusStorage,
}

expMeta := latest.GroupOrDie("extensions")
Expand Down
19 changes: 17 additions & 2 deletions pkg/registry/ingress/etcd/etcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type REST struct {
}

// NewREST returns a RESTStorage object that will work against replication controllers.
func NewREST(s storage.Interface) *REST {
func NewREST(s storage.Interface) (*REST, *StatusREST) {
store := &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &extensions.Ingress{} },

Expand Down Expand Up @@ -72,6 +72,21 @@ func NewREST(s storage.Interface) *REST {

Storage: s,
}
statusStore := *store
statusStore.UpdateStrategy = ingress.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}
}

// StatusREST implements the REST endpoint for changing the status of an ingress
type StatusREST struct {
store *etcdgeneric.Etcd
}

func (r *StatusREST) New() runtime.Object {
return &extensions.Ingress{}
}

return &REST{store}
// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
return r.store.Update(ctx, obj)
}
20 changes: 11 additions & 9 deletions pkg/registry/ingress/etcd/etcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ import (
"k8s.io/kubernetes/pkg/util"
)

func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient) {
func newStorage(t *testing.T) (*REST, *StatusREST, *tools.FakeEtcdClient) {
etcdStorage, fakeClient := registrytest.NewEtcdStorage(t, "extensions")
ingressStorage := NewREST(etcdStorage)
return ingressStorage, fakeClient
ingressStorage, statusStorage := NewREST(etcdStorage)
return ingressStorage, statusStorage, fakeClient
}

var (
Expand Down Expand Up @@ -107,7 +107,7 @@ func validIngress() *extensions.Ingress {
}

func TestCreate(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
ingress := validIngress()
noDefaultBackendAndRules := validIngress()
Expand All @@ -125,7 +125,7 @@ func TestCreate(t *testing.T) {
}

func TestUpdate(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestUpdate(
// valid
Expand Down Expand Up @@ -161,25 +161,25 @@ func TestUpdate(t *testing.T) {
}

func TestDelete(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestDelete(validIngress())
}

func TestGet(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestGet(validIngress())
}

func TestList(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestList(validIngress())
}

func TestWatch(t *testing.T) {
storage, fakeClient := newStorage(t)
storage, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestWatch(
validIngress(),
Expand All @@ -201,3 +201,5 @@ func TestWatch(t *testing.T) {
},
)
}

// TODO TestUpdateStatus
23 changes: 22 additions & 1 deletion pkg/registry/ingress/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func (ingressStrategy) NamespaceScoped() bool {
// PrepareForCreate clears the status of an Ingress before creation.
func (ingressStrategy) PrepareForCreate(obj runtime.Object) {
ingress := obj.(*extensions.Ingress)
// create cannot set status
ingress.Status = extensions.IngressStatus{}

ingress.Generation = 1
Expand All @@ -56,7 +57,8 @@ func (ingressStrategy) PrepareForCreate(obj runtime.Object) {
func (ingressStrategy) PrepareForUpdate(obj, old runtime.Object) {
newIngress := obj.(*extensions.Ingress)
oldIngress := old.(*extensions.Ingress)
//TODO: Clear Ingress status once we have a sub-resource.
// Update is not allowed to set status
newIngress.Status = oldIngress.Status

// Any changes to the spec increment the generation number, any changes to the
// status should reflect the generation number of the corresponding object.
Expand Down Expand Up @@ -114,3 +116,22 @@ func MatchIngress(label labels.Selector, field fields.Selector) generic.Matcher
},
}
}

type ingressStatusStrategy struct {
ingressStrategy
}

var StatusStrategy = ingressStatusStrategy{Strategy}

// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status
func (ingressStatusStrategy) PrepareForUpdate(obj, old runtime.Object) {
newIngress := obj.(*extensions.Ingress)
oldIngress := old.(*extensions.Ingress)
// status changes are not allowed to update spec
newIngress.Spec = oldIngress.Spec
}

// ValidateUpdate is the default update validation for an end user updating status
func (ingressStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList {
return validation.ValidateIngressStatusUpdate(obj.(*extensions.Ingress), old.(*extensions.Ingress))
}
Loading

1 comment on commit eae56c3

@k8s-teamcity-mesosphere

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity OSS :: Kubernetes Mesos :: 5 - Smoke Tests (docker/mesos) Build 867 outcome was SUCCESS
Summary: Tests passed: 1, ignored: 190 Build time: 00:04:00

Please sign in to comment.