mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 04:33:26 +00:00
services strategy no longer depends on IPFamilies
since the refactor on the Service API registry, the strategy for service no longer needs to keep information about the cluster configuration and its ipFamilies.
This commit is contained in:
parent
5f40fb05cb
commit
975a678ecf
@ -41,7 +41,6 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/printers"
|
"k8s.io/kubernetes/pkg/printers"
|
||||||
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||||
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
|
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
|
||||||
"k8s.io/kubernetes/pkg/registry/core/service"
|
|
||||||
svcreg "k8s.io/kubernetes/pkg/registry/core/service"
|
svcreg "k8s.io/kubernetes/pkg/registry/core/service"
|
||||||
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
||||||
"k8s.io/kubernetes/pkg/registry/core/service/portallocator"
|
"k8s.io/kubernetes/pkg/registry/core/service/portallocator"
|
||||||
@ -86,18 +85,16 @@ func NewREST(
|
|||||||
pods PodStorage,
|
pods PodStorage,
|
||||||
proxyTransport http.RoundTripper) (*REST, *StatusREST, *svcreg.ProxyREST, error) {
|
proxyTransport http.RoundTripper) (*REST, *StatusREST, *svcreg.ProxyREST, error) {
|
||||||
|
|
||||||
strategy, _ := svcreg.StrategyForServiceCIDRs(ipAllocs[serviceIPFamily].CIDR(), len(ipAllocs) > 1)
|
|
||||||
|
|
||||||
store := &genericregistry.Store{
|
store := &genericregistry.Store{
|
||||||
NewFunc: func() runtime.Object { return &api.Service{} },
|
NewFunc: func() runtime.Object { return &api.Service{} },
|
||||||
NewListFunc: func() runtime.Object { return &api.ServiceList{} },
|
NewListFunc: func() runtime.Object { return &api.ServiceList{} },
|
||||||
DefaultQualifiedResource: api.Resource("services"),
|
DefaultQualifiedResource: api.Resource("services"),
|
||||||
ReturnDeletedObject: true,
|
ReturnDeletedObject: true,
|
||||||
|
|
||||||
CreateStrategy: strategy,
|
CreateStrategy: svcreg.Strategy,
|
||||||
UpdateStrategy: strategy,
|
UpdateStrategy: svcreg.Strategy,
|
||||||
DeleteStrategy: strategy,
|
DeleteStrategy: svcreg.Strategy,
|
||||||
ResetFieldsStrategy: strategy,
|
ResetFieldsStrategy: svcreg.Strategy,
|
||||||
|
|
||||||
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
|
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
|
||||||
}
|
}
|
||||||
@ -107,9 +104,8 @@ func NewREST(
|
|||||||
}
|
}
|
||||||
|
|
||||||
statusStore := *store
|
statusStore := *store
|
||||||
statusStrategy := service.NewServiceStatusStrategy(strategy)
|
statusStore.UpdateStrategy = svcreg.StatusStrategy
|
||||||
statusStore.UpdateStrategy = statusStrategy
|
statusStore.ResetFieldsStrategy = svcreg.StatusStrategy
|
||||||
statusStore.ResetFieldsStrategy = statusStrategy
|
|
||||||
|
|
||||||
var primaryIPFamily api.IPFamily = serviceIPFamily
|
var primaryIPFamily api.IPFamily = serviceIPFamily
|
||||||
var secondaryIPFamily api.IPFamily = "" // sentinel value
|
var secondaryIPFamily api.IPFamily = "" // sentinel value
|
||||||
|
@ -18,74 +18,29 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
netutil "k8s.io/utils/net"
|
|
||||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Strategy interface {
|
|
||||||
rest.RESTCreateUpdateStrategy
|
|
||||||
rest.ResetFieldsStrategy
|
|
||||||
}
|
|
||||||
|
|
||||||
// svcStrategy implements behavior for Services
|
// svcStrategy implements behavior for Services
|
||||||
type svcStrategy struct {
|
type svcStrategy struct {
|
||||||
runtime.ObjectTyper
|
runtime.ObjectTyper
|
||||||
names.NameGenerator
|
names.NameGenerator
|
||||||
|
|
||||||
ipFamilies []api.IPFamily
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrategyForServiceCIDRs returns the appropriate service strategy for the given configuration.
|
// Strategy is the default logic that applies when creating and updating Services
|
||||||
func StrategyForServiceCIDRs(primaryCIDR net.IPNet, hasSecondary bool) (Strategy, api.IPFamily) {
|
// objects via the REST API.
|
||||||
// detect this cluster default Service IPFamily (ipfamily of --service-cluster-ip-range)
|
var Strategy = svcStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
|
||||||
// we do it once here, to avoid having to do it over and over during ipfamily assignment
|
|
||||||
serviceIPFamily := api.IPv4Protocol
|
|
||||||
if netutil.IsIPv6CIDR(&primaryCIDR) {
|
|
||||||
serviceIPFamily = api.IPv6Protocol
|
|
||||||
}
|
|
||||||
|
|
||||||
var strategy Strategy
|
|
||||||
switch {
|
|
||||||
case hasSecondary && serviceIPFamily == api.IPv4Protocol:
|
|
||||||
strategy = svcStrategy{
|
|
||||||
ObjectTyper: legacyscheme.Scheme,
|
|
||||||
NameGenerator: names.SimpleNameGenerator,
|
|
||||||
ipFamilies: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol},
|
|
||||||
}
|
|
||||||
case hasSecondary && serviceIPFamily == api.IPv6Protocol:
|
|
||||||
strategy = svcStrategy{
|
|
||||||
ObjectTyper: legacyscheme.Scheme,
|
|
||||||
NameGenerator: names.SimpleNameGenerator,
|
|
||||||
ipFamilies: []api.IPFamily{api.IPv6Protocol, api.IPv4Protocol},
|
|
||||||
}
|
|
||||||
case serviceIPFamily == api.IPv6Protocol:
|
|
||||||
strategy = svcStrategy{
|
|
||||||
ObjectTyper: legacyscheme.Scheme,
|
|
||||||
NameGenerator: names.SimpleNameGenerator,
|
|
||||||
ipFamilies: []api.IPFamily{api.IPv6Protocol},
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
strategy = svcStrategy{
|
|
||||||
ObjectTyper: legacyscheme.Scheme,
|
|
||||||
NameGenerator: names.SimpleNameGenerator,
|
|
||||||
ipFamilies: []api.IPFamily{api.IPv4Protocol},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strategy, serviceIPFamily
|
|
||||||
}
|
|
||||||
|
|
||||||
// NamespaceScoped is true for services.
|
// NamespaceScoped is true for services.
|
||||||
func (svcStrategy) NamespaceScoped() bool {
|
func (svcStrategy) NamespaceScoped() bool {
|
||||||
@ -105,7 +60,7 @@ func (svcStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PrepareForCreate sets contextual defaults and clears fields that are not allowed to be set by end users on creation.
|
// PrepareForCreate sets contextual defaults and clears fields that are not allowed to be set by end users on creation.
|
||||||
func (strategy svcStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
func (svcStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||||
service := obj.(*api.Service)
|
service := obj.(*api.Service)
|
||||||
service.Status = api.ServiceStatus{}
|
service.Status = api.ServiceStatus{}
|
||||||
|
|
||||||
@ -113,7 +68,7 @@ func (strategy svcStrategy) PrepareForCreate(ctx context.Context, obj runtime.Ob
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PrepareForUpdate sets contextual defaults and clears fields that are not allowed to be set by end users on update.
|
// PrepareForUpdate sets contextual defaults and clears fields that are not allowed to be set by end users on update.
|
||||||
func (strategy svcStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
func (svcStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||||
newService := obj.(*api.Service)
|
newService := obj.(*api.Service)
|
||||||
oldService := old.(*api.Service)
|
oldService := old.(*api.Service)
|
||||||
newService.Status = oldService.Status
|
newService.Status = oldService.Status
|
||||||
@ -123,7 +78,7 @@ func (strategy svcStrategy) PrepareForUpdate(ctx context.Context, obj, old runti
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates a new service.
|
// Validate validates a new service.
|
||||||
func (strategy svcStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (svcStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
service := obj.(*api.Service)
|
service := obj.(*api.Service)
|
||||||
allErrs := validation.ValidateServiceCreate(service)
|
allErrs := validation.ValidateServiceCreate(service)
|
||||||
allErrs = append(allErrs, validation.ValidateConditionalService(service, nil)...)
|
allErrs = append(allErrs, validation.ValidateConditionalService(service, nil)...)
|
||||||
@ -211,13 +166,11 @@ func serviceInternalTrafficPolicyInUse(svc *api.Service) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type serviceStatusStrategy struct {
|
type serviceStatusStrategy struct {
|
||||||
Strategy
|
svcStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServiceStatusStrategy creates a status strategy for the provided base strategy.
|
// StatusStrategy wraps and exports the used svcStrategy for the storage package.
|
||||||
func NewServiceStatusStrategy(strategy Strategy) Strategy {
|
var StatusStrategy = serviceStatusStrategy{Strategy}
|
||||||
return serviceStatusStrategy{strategy}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResetFields returns the set of fields that get reset by the strategy
|
// GetResetFields returns the set of fields that get reset by the strategy
|
||||||
// and should not be modified by the user.
|
// and should not be modified by the user.
|
||||||
|
@ -31,38 +31,26 @@ import (
|
|||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
netutils "k8s.io/utils/net"
|
|
||||||
utilpointer "k8s.io/utils/pointer"
|
utilpointer "k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newStrategy(cidr string, hasSecondary bool) (testStrategy Strategy, testStatusStrategy Strategy) {
|
|
||||||
_, testCIDR, err := netutils.ParseCIDRSloppy(cidr)
|
|
||||||
if err != nil {
|
|
||||||
panic("invalid CIDR")
|
|
||||||
}
|
|
||||||
testStrategy, _ = StrategyForServiceCIDRs(*testCIDR, hasSecondary)
|
|
||||||
testStatusStrategy = NewServiceStatusStrategy(testStrategy)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckGeneratedNameError(t *testing.T) {
|
func TestCheckGeneratedNameError(t *testing.T) {
|
||||||
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
|
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
|
||||||
Resource: "foos",
|
Resource: "foos",
|
||||||
})
|
})
|
||||||
|
|
||||||
testStrategy, _ := newStrategy("10.0.0.0/16", false)
|
|
||||||
expect := errors.NewNotFound(api.Resource("foos"), "bar")
|
expect := errors.NewNotFound(api.Resource("foos"), "bar")
|
||||||
if err := rest.CheckGeneratedNameError(ctx, testStrategy, expect, &api.Service{}); err != expect {
|
if err := rest.CheckGeneratedNameError(ctx, Strategy, expect, &api.Service{}); err != expect {
|
||||||
t.Errorf("NotFoundError should be ignored: %v", err)
|
t.Errorf("NotFoundError should be ignored: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expect = errors.NewAlreadyExists(api.Resource("foos"), "bar")
|
expect = errors.NewAlreadyExists(api.Resource("foos"), "bar")
|
||||||
if err := rest.CheckGeneratedNameError(ctx, testStrategy, expect, &api.Service{}); err != expect {
|
if err := rest.CheckGeneratedNameError(ctx, Strategy, expect, &api.Service{}); err != expect {
|
||||||
t.Errorf("AlreadyExists should be returned when no GenerateName field: %v", err)
|
t.Errorf("AlreadyExists should be returned when no GenerateName field: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expect = errors.NewAlreadyExists(api.Resource("foos"), "bar")
|
expect = errors.NewAlreadyExists(api.Resource("foos"), "bar")
|
||||||
if err := rest.CheckGeneratedNameError(ctx, testStrategy, expect, &api.Service{ObjectMeta: metav1.ObjectMeta{GenerateName: "foo"}}); err == nil || !errors.IsAlreadyExists(err) {
|
if err := rest.CheckGeneratedNameError(ctx, Strategy, expect, &api.Service{ObjectMeta: metav1.ObjectMeta{GenerateName: "foo"}}); err == nil || !errors.IsAlreadyExists(err) {
|
||||||
t.Errorf("expected try again later error: %v", err)
|
t.Errorf("expected try again later error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,9 +102,8 @@ func makeValidServiceCustom(tweaks ...func(svc *api.Service)) *api.Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceStatusStrategy(t *testing.T) {
|
func TestServiceStatusStrategy(t *testing.T) {
|
||||||
_, testStatusStrategy := newStrategy("10.0.0.0/16", false)
|
|
||||||
ctx := genericapirequest.NewDefaultContext()
|
ctx := genericapirequest.NewDefaultContext()
|
||||||
if !testStatusStrategy.NamespaceScoped() {
|
if !StatusStrategy.NamespaceScoped() {
|
||||||
t.Errorf("Service must be namespace scoped")
|
t.Errorf("Service must be namespace scoped")
|
||||||
}
|
}
|
||||||
oldService := makeValidService()
|
oldService := makeValidService()
|
||||||
@ -131,14 +118,14 @@ func TestServiceStatusStrategy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
testStatusStrategy.PrepareForUpdate(ctx, newService, oldService)
|
StatusStrategy.PrepareForUpdate(ctx, newService, oldService)
|
||||||
if newService.Status.LoadBalancer.Ingress[0].IP != "127.0.0.2" {
|
if newService.Status.LoadBalancer.Ingress[0].IP != "127.0.0.2" {
|
||||||
t.Errorf("Service status updates should allow change of status fields")
|
t.Errorf("Service status updates should allow change of status fields")
|
||||||
}
|
}
|
||||||
if newService.Spec.SessionAffinity != "None" {
|
if newService.Spec.SessionAffinity != "None" {
|
||||||
t.Errorf("PrepareForUpdate should have preserved old spec")
|
t.Errorf("PrepareForUpdate should have preserved old spec")
|
||||||
}
|
}
|
||||||
errs := testStatusStrategy.ValidateUpdate(ctx, newService, oldService)
|
errs := StatusStrategy.ValidateUpdate(ctx, newService, oldService)
|
||||||
if len(errs) != 0 {
|
if len(errs) != 0 {
|
||||||
t.Errorf("Unexpected error %v", errs)
|
t.Errorf("Unexpected error %v", errs)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user