Allow an empty service

This commit is contained in:
Clayton Coleman
2014-11-18 12:49:00 -05:00
parent 7f2d0c0f71
commit 2c27f7d332
13 changed files with 198 additions and 32 deletions

View File

@@ -593,8 +593,10 @@ type ServiceSpec struct {
// Optional: Supports "TCP" and "UDP". Defaults to "TCP".
Protocol Protocol `json:"protocol,omitempty"`
// This service will route traffic to pods having labels matching this selector.
Selector map[string]string `json:"selector,omitempty"`
// This service will route traffic to pods having labels matching this selector. If empty or not present,
// the service is assumed to have endpoints set by an external process and Kubernetes will not modify
// those endpoints.
Selector map[string]string `json:"selector"`
// PortalIP is usually assigned by the master. If specified by the user
// we will try to respect it or else fail the request. This field can

View File

@@ -192,3 +192,35 @@ func TestMinionListConversionToOld(t *testing.T) {
t.Errorf("Expected: %#v, got %#v", e, a)
}
}
func TestServiceEmptySelector(t *testing.T) {
// Nil map should be preserved
svc := &v1beta1.Service{Selector: nil}
data, err := newer.Scheme.EncodeToVersion(svc, "v1beta1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
obj, err := newer.Scheme.Decode(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
selector := obj.(*newer.Service).Spec.Selector
if selector != nil {
t.Errorf("unexpected selector: %#v", obj)
}
// Empty map should be preserved
svc2 := &v1beta1.Service{Selector: map[string]string{}}
data, err = newer.Scheme.EncodeToVersion(svc2, "v1beta1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
obj, err = newer.Scheme.Decode(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
selector = obj.(*newer.Service).Spec.Selector
if selector == nil || len(selector) != 0 {
t.Errorf("unexpected selector: %#v", obj)
}
}

View File

@@ -465,9 +465,11 @@ type Service struct {
// This service's labels.
Labels map[string]string `json:"labels,omitempty" description:"map of string keys and values that can be used to organize and categorize services"`
// This service will route traffic to pods having labels matching this selector.
Selector map[string]string `json:"selector,omitempty" description:"label keys and values that must match in order to receive traffic for this service"`
CreateExternalLoadBalancer bool `json:"createExternalLoadBalancer,omitempty" description:"set up a cloud-provider-specific load balancer on an external IP"`
// This service will route traffic to pods having labels matching this selector. If null, no endpoints will be automatically created. If empty, all pods will be selected.
Selector map[string]string `json:"selector" description:"label keys and values that must match in order to receive traffic for this service; if empty, all pods are selected, if not specified, endpoints must be manually specified"`
// An external load balancer should be set up via the cloud-provider
CreateExternalLoadBalancer bool `json:"createExternalLoadBalancer,omitempty" description:"set up a cloud-provider-specific load balancer on an external IP"`
// PublicIPs are used by external load balancers.
PublicIPs []string `json:"publicIPs,omitempty" description:"externally visible IPs from which to select the address for the external load balancer"`

View File

@@ -16,4 +16,41 @@ limitations under the License.
package v1beta2_test
import ()
import (
"testing"
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
)
func TestServiceEmptySelector(t *testing.T) {
// Nil map should be preserved
svc := &v1beta2.Service{Selector: nil}
data, err := newer.Scheme.EncodeToVersion(svc, "v1beta2")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
obj, err := newer.Scheme.Decode(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
selector := obj.(*newer.Service).Spec.Selector
if selector != nil {
t.Errorf("unexpected selector: %#v", obj)
}
// Empty map should be preserved
svc2 := &v1beta2.Service{Selector: map[string]string{}}
data, err = newer.Scheme.EncodeToVersion(svc2, "v1beta2")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
obj, err = newer.Scheme.Decode(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
selector = obj.(*newer.Service).Spec.Selector
if selector == nil || len(selector) != 0 {
t.Errorf("unexpected selector: %#v", obj)
}
}

View File

@@ -430,9 +430,11 @@ type Service struct {
// This service's labels.
Labels map[string]string `json:"labels,omitempty" description:"map of string keys and values that can be used to organize and categorize services"`
// This service will route traffic to pods having labels matching this selector.
Selector map[string]string `json:"selector,omitempty" description:"label keys and values that must match in order to receive traffic for this service"`
CreateExternalLoadBalancer bool `json:"createExternalLoadBalancer,omitempty" description:"set up a cloud-provider-specific load balancer on an external IP"`
// This service will route traffic to pods having labels matching this selector. If null, no endpoints will be automatically created. If empty, all pods will be selected.
Selector map[string]string `json:"selector" description:"label keys and values that must match in order to receive traffic for this service; if empty, all pods are selected, if not specified, endpoints must be manually specified"`
// An external load balancer should be set up via the cloud-provider
CreateExternalLoadBalancer bool `json:"createExternalLoadBalancer,omitempty" description:"set up a cloud-provider-specific load balancer on an external IP"`
// PublicIPs are used by external load balancers.
PublicIPs []string `json:"publicIPs,omitempty" description:"externally visible IPs from which to select the address for the external load balancer"`

View File

@@ -601,8 +601,8 @@ type ServiceSpec struct {
// Optional: Supports "TCP" and "UDP". Defaults to "TCP".
Protocol Protocol `json:"protocol,omitempty"`
// This service will route traffic to pods having labels matching this selector.
Selector map[string]string `json:"selector,omitempty"`
// This service will route traffic to pods having labels matching this selector. If null, no endpoints will be automatically created. If empty, all pods will be selected.
Selector map[string]string `json:"selector"`
// PortalIP is usually assigned by the master. If specified by the user
// we will try to respect it or else fail the request. This field can

View File

@@ -438,9 +438,12 @@ func ValidateService(service *api.Service, lister ServiceLister, ctx api.Context
} else if !supportedPortProtocols.Has(strings.ToUpper(string(service.Spec.Protocol))) {
allErrs = append(allErrs, errs.NewFieldNotSupported("spec.protocol", service.Spec.Protocol))
}
if labels.Set(service.Spec.Selector).AsSelector().Empty() {
allErrs = append(allErrs, errs.NewFieldRequired("spec.selector", service.Spec.Selector))
if service.Spec.Selector != nil {
allErrs = append(allErrs, validateLabels(service.Spec.Selector, "spec.selector")...)
}
allErrs = append(allErrs, validateLabels(service.Labels, "labels")...)
if service.Spec.CreateExternalLoadBalancer {
services, err := lister.ListServices(ctx)
if err != nil {
@@ -456,8 +459,6 @@ func ValidateService(service *api.Service, lister ServiceLister, ctx api.Context
}
}
}
allErrs = append(allErrs, validateLabels(service.Labels, "labels")...)
allErrs = append(allErrs, validateLabels(service.Spec.Selector, "selector")...)
return allErrs
}

View File

@@ -710,8 +710,8 @@ func TestValidateService(t *testing.T) {
Port: 8675,
},
},
// Should fail because the selector is missing.
numErrs: 1,
// Should be ok because the selector is missing.
numErrs: 0,
},
{
name: "valid 1",
@@ -824,12 +824,25 @@ func TestValidateService(t *testing.T) {
"NoUppercaseOrSpecialCharsLike=Equals": "bar",
},
},
Spec: api.ServiceSpec{
Port: 8675,
},
},
numErrs: 1,
},
{
name: "invalid selector",
svc: api.Service{
ObjectMeta: api.ObjectMeta{
Name: "abc123",
Namespace: api.NamespaceDefault,
},
Spec: api.ServiceSpec{
Port: 8675,
Selector: map[string]string{"foo": "bar", "NoUppercaseOrSpecialCharsLike=Equals": "bar"},
},
},
numErrs: 2,
numErrs: 1,
},
}