Skip to content

Commit c7ff310

Browse files
committed
Merge pull request kubernetes#2094 from brendandburns/svc
Add some validation for externalized service ports.
2 parents 437f90d + f556f2f commit c7ff310

File tree

6 files changed

+110
-9
lines changed

6 files changed

+110
-9
lines changed

examples/examples_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
2828
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
2929
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
30+
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
3031
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
3132
"github.com/golang/glog"
3233
)
@@ -42,7 +43,7 @@ func validateObject(obj runtime.Object) (errors []error) {
4243
}
4344
case *api.Service:
4445
api.ValidNamespace(ctx, &t.ObjectMeta)
45-
errors = validation.ValidateService(t)
46+
errors = validation.ValidateService(t, registrytest.NewServiceRegistry(), api.NewDefaultContext())
4647
case *api.ServiceList:
4748
for i := range t.Items {
4849
errors = append(errors, validateObject(&t.Items[i])...)

pkg/api/errors/errors.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,19 @@ func NewBadRequest(reason string) error {
132132
}
133133
}
134134

135+
// NewInternalError returns an error indicating the item is invalid and cannot be processed.
136+
func NewInternalError(err error) error {
137+
return &statusError{api.Status{
138+
Status: api.StatusFailure,
139+
Code: 500,
140+
Reason: api.StatusReasonInternalError,
141+
Details: &api.StatusDetails{
142+
Causes: []api.StatusCause{{Message: err.Error()}},
143+
},
144+
Message: fmt.Sprintf("Internal error occurred: %v", err),
145+
}}
146+
}
147+
135148
// IsNotFound returns true if the specified error was created by NewNotFoundErr.
136149
func IsNotFound(err error) bool {
137150
return reasonForError(err) == api.StatusReasonNotFound

pkg/api/types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,13 @@ const (
686686
// StatusReasonInvalid above which indicates that the API call could possibly succeed, but the
687687
// data was invalid. API calls that return BadRequest can never succeed.
688688
StatusReasonBadRequest StatusReason = "BadRequest"
689+
690+
// StatusReasonInternalError indicates that an internal error occurred, it is unexpected
691+
// and the outcome of the call is unknown.
692+
// Details (optional):
693+
// "causes" - The original error
694+
// Status code 500
695+
StatusReasonInternalError = "InternalError"
689696
)
690697

691698
// StatusCause provides more information about an api.Status failure, including

pkg/api/validation/validation.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package validation
1818

1919
import (
20+
"fmt"
2021
"reflect"
2122
"strings"
2223

@@ -27,6 +28,10 @@ import (
2728
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
2829
)
2930

31+
type ServiceLister interface {
32+
ListServices(api.Context) (*api.ServiceList, error)
33+
}
34+
3035
func validateVolumes(volumes []api.Volume) (util.StringSet, errs.ValidationErrorList) {
3136
allErrs := errs.ValidationErrorList{}
3237

@@ -382,7 +387,7 @@ func ValidatePodUpdate(newPod, oldPod *api.Pod) errs.ValidationErrorList {
382387
}
383388

384389
// ValidateService tests if required fields in the service are set.
385-
func ValidateService(service *api.Service) errs.ValidationErrorList {
390+
func ValidateService(service *api.Service, lister ServiceLister, ctx api.Context) errs.ValidationErrorList {
386391
allErrs := errs.ValidationErrorList{}
387392
if len(service.Name) == 0 {
388393
allErrs = append(allErrs, errs.NewFieldRequired("name", service.Name))
@@ -403,6 +408,19 @@ func ValidateService(service *api.Service) errs.ValidationErrorList {
403408
if labels.Set(service.Selector).AsSelector().Empty() {
404409
allErrs = append(allErrs, errs.NewFieldRequired("selector", service.Selector))
405410
}
411+
if service.CreateExternalLoadBalancer {
412+
services, err := lister.ListServices(ctx)
413+
if err != nil {
414+
allErrs = append(allErrs, errs.NewInternalError(err))
415+
} else {
416+
for i := range services.Items {
417+
if services.Items[i].CreateExternalLoadBalancer && services.Items[i].Port == service.Port {
418+
allErrs = append(allErrs, errs.NewConflict("service", service.Namespace, fmt.Errorf("Port: %d is already in use", service.Port)))
419+
break
420+
}
421+
}
422+
}
423+
}
406424
allErrs = append(allErrs, validateLabels(service.Labels)...)
407425
allErrs = append(allErrs, validateLabels(service.Selector)...)
408426
return allErrs

pkg/api/validation/validation_test.go

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
2424
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
2525
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
26+
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
2627
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
2728
)
2829

@@ -625,9 +626,10 @@ func TestValidatePodUpdate(t *testing.T) {
625626

626627
func TestValidateService(t *testing.T) {
627628
testCases := []struct {
628-
name string
629-
svc api.Service
630-
numErrs int
629+
name string
630+
svc api.Service
631+
existing api.ServiceList
632+
numErrs int
631633
}{
632634
{
633635
name: "missing id",
@@ -727,6 +729,64 @@ func TestValidateService(t *testing.T) {
727729
},
728730
numErrs: 0,
729731
},
732+
{
733+
name: "invalid port in use",
734+
svc: api.Service{
735+
ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault},
736+
Port: 80,
737+
CreateExternalLoadBalancer: true,
738+
Selector: map[string]string{"foo": "bar"},
739+
},
740+
existing: api.ServiceList{
741+
Items: []api.Service{
742+
{Port: 80, CreateExternalLoadBalancer: true},
743+
},
744+
},
745+
numErrs: 1,
746+
},
747+
{
748+
name: "same port in use, but not external",
749+
svc: api.Service{
750+
ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault},
751+
Port: 80,
752+
CreateExternalLoadBalancer: true,
753+
Selector: map[string]string{"foo": "bar"},
754+
},
755+
existing: api.ServiceList{
756+
Items: []api.Service{
757+
{Port: 80},
758+
},
759+
},
760+
numErrs: 0,
761+
},
762+
{
763+
name: "same port in use, but not external on input",
764+
svc: api.Service{
765+
ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault},
766+
Port: 80,
767+
Selector: map[string]string{"foo": "bar"},
768+
},
769+
existing: api.ServiceList{
770+
Items: []api.Service{
771+
{Port: 80, CreateExternalLoadBalancer: true},
772+
},
773+
},
774+
numErrs: 0,
775+
},
776+
{
777+
name: "same port in use, but neither external",
778+
svc: api.Service{
779+
ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault},
780+
Port: 80,
781+
Selector: map[string]string{"foo": "bar"},
782+
},
783+
existing: api.ServiceList{
784+
Items: []api.Service{
785+
{Port: 80},
786+
},
787+
},
788+
numErrs: 0,
789+
},
730790
{
731791
name: "invalid label",
732792
svc: api.Service{
@@ -745,7 +805,9 @@ func TestValidateService(t *testing.T) {
745805
}
746806

747807
for _, tc := range testCases {
748-
errs := ValidateService(&tc.svc)
808+
registry := registrytest.NewServiceRegistry()
809+
registry.List = tc.existing
810+
errs := ValidateService(&tc.svc, registry, api.NewDefaultContext())
749811
if len(errs) != tc.numErrs {
750812
t.Errorf("Unexpected error list for case %q: %+v", tc.name, errs)
751813
}
@@ -756,7 +818,7 @@ func TestValidateService(t *testing.T) {
756818
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault},
757819
Selector: map[string]string{"foo": "bar"},
758820
}
759-
errs := ValidateService(&svc)
821+
errs := ValidateService(&svc, registrytest.NewServiceRegistry(), api.NewDefaultContext())
760822
if len(errs) != 0 {
761823
t.Errorf("Unexpected non-zero error list: %#v", errs)
762824
}

pkg/registry/service/rest.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
8787
if !api.ValidNamespace(ctx, &service.ObjectMeta) {
8888
return nil, errors.NewConflict("service", service.Namespace, fmt.Errorf("Service.Namespace does not match the provided context"))
8989
}
90-
if errs := validation.ValidateService(service); len(errs) > 0 {
90+
if errs := validation.ValidateService(service, rs.registry, ctx); len(errs) > 0 {
9191
return nil, errors.NewInvalid("service", service.Name, errs)
9292
}
9393

@@ -229,7 +229,7 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
229229
if !api.ValidNamespace(ctx, &service.ObjectMeta) {
230230
return nil, errors.NewConflict("service", service.Namespace, fmt.Errorf("Service.Namespace does not match the provided context"))
231231
}
232-
if errs := validation.ValidateService(service); len(errs) > 0 {
232+
if errs := validation.ValidateService(service, rs.registry, ctx); len(errs) > 0 {
233233
return nil, errors.NewInvalid("service", service.Name, errs)
234234
}
235235
return apiserver.MakeAsync(func() (runtime.Object, error) {

0 commit comments

Comments
 (0)