Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add status subresource to Ingress #15491

Merged
merged 1 commit into from
Oct 15, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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"))
Copy link
Member

Choose a reason for hiding this comment

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

s/ip IP/

}
}
}
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))
}