123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- /*
- Copyright 2014 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package service
- import (
- "reflect"
- "testing"
- "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/util/diff"
- "k8s.io/apimachinery/pkg/util/intstr"
- genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
- "k8s.io/apiserver/pkg/registry/rest"
- api "k8s.io/kubernetes/pkg/apis/core"
- _ "k8s.io/kubernetes/pkg/apis/core/install"
- utilfeature "k8s.io/apiserver/pkg/util/feature"
- featuregatetesting "k8s.io/component-base/featuregate/testing"
- "k8s.io/kubernetes/pkg/features"
- )
- func TestExportService(t *testing.T) {
- tests := []struct {
- objIn runtime.Object
- objOut runtime.Object
- exact bool
- expectErr bool
- }{
- {
- objIn: &api.Service{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Namespace: "bar",
- },
- Status: api.ServiceStatus{
- LoadBalancer: api.LoadBalancerStatus{
- Ingress: []api.LoadBalancerIngress{
- {IP: "1.2.3.4"},
- },
- },
- },
- },
- objOut: &api.Service{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Namespace: "bar",
- },
- },
- exact: true,
- },
- {
- objIn: &api.Service{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Namespace: "bar",
- },
- Spec: api.ServiceSpec{
- ClusterIP: "10.0.0.1",
- },
- Status: api.ServiceStatus{
- LoadBalancer: api.LoadBalancerStatus{
- Ingress: []api.LoadBalancerIngress{
- {IP: "1.2.3.4"},
- },
- },
- },
- },
- objOut: &api.Service{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Namespace: "bar",
- },
- Spec: api.ServiceSpec{
- ClusterIP: "",
- },
- },
- },
- {
- objIn: &api.Pod{},
- expectErr: true,
- },
- }
- for _, test := range tests {
- err := Strategy.Export(genericapirequest.NewContext(), test.objIn, test.exact)
- if err != nil {
- if !test.expectErr {
- t.Errorf("unexpected error: %v", err)
- }
- continue
- }
- if test.expectErr {
- t.Error("unexpected non-error")
- continue
- }
- if !reflect.DeepEqual(test.objIn, test.objOut) {
- t.Errorf("expected:\n%v\nsaw:\n%v\n", test.objOut, test.objIn)
- }
- }
- }
- func TestCheckGeneratedNameError(t *testing.T) {
- expect := errors.NewNotFound(api.Resource("foos"), "bar")
- if err := rest.CheckGeneratedNameError(Strategy, expect, &api.Service{}); err != expect {
- t.Errorf("NotFoundError should be ignored: %v", err)
- }
- expect = errors.NewAlreadyExists(api.Resource("foos"), "bar")
- if err := rest.CheckGeneratedNameError(Strategy, expect, &api.Service{}); err != expect {
- t.Errorf("AlreadyExists should be returned when no GenerateName field: %v", err)
- }
- expect = errors.NewAlreadyExists(api.Resource("foos"), "bar")
- if err := rest.CheckGeneratedNameError(Strategy, expect, &api.Service{ObjectMeta: metav1.ObjectMeta{GenerateName: "foo"}}); err == nil || !errors.IsServerTimeout(err) {
- t.Errorf("expected try again later error: %v", err)
- }
- }
- func makeValidService() api.Service {
- defaultServiceIPFamily := api.IPv4Protocol
- return api.Service{
- ObjectMeta: metav1.ObjectMeta{
- Name: "valid",
- Namespace: "default",
- Labels: map[string]string{},
- Annotations: map[string]string{},
- ResourceVersion: "1",
- },
- Spec: api.ServiceSpec{
- Selector: map[string]string{"key": "val"},
- SessionAffinity: "None",
- Type: api.ServiceTypeClusterIP,
- Ports: []api.ServicePort{{Name: "p", Protocol: "TCP", Port: 8675, TargetPort: intstr.FromInt(8675)}},
- IPFamily: &defaultServiceIPFamily,
- },
- }
- }
- // TODO: This should be done on types that are not part of our API
- func TestBeforeUpdate(t *testing.T) {
- testCases := []struct {
- name string
- tweakSvc func(oldSvc, newSvc *api.Service) // given basic valid services, each test case can customize them
- expectErr bool
- }{
- {
- name: "no change",
- tweakSvc: func(oldSvc, newSvc *api.Service) {
- // nothing
- },
- expectErr: false,
- },
- {
- name: "change port",
- tweakSvc: func(oldSvc, newSvc *api.Service) {
- newSvc.Spec.Ports[0].Port++
- },
- expectErr: false,
- },
- {
- name: "bad namespace",
- tweakSvc: func(oldSvc, newSvc *api.Service) {
- newSvc.Namespace = "#$%%invalid"
- },
- expectErr: true,
- },
- {
- name: "change name",
- tweakSvc: func(oldSvc, newSvc *api.Service) {
- newSvc.Name += "2"
- },
- expectErr: true,
- },
- {
- name: "change ClusterIP",
- tweakSvc: func(oldSvc, newSvc *api.Service) {
- oldSvc.Spec.ClusterIP = "1.2.3.4"
- newSvc.Spec.ClusterIP = "4.3.2.1"
- },
- expectErr: true,
- },
- {
- name: "change selector",
- tweakSvc: func(oldSvc, newSvc *api.Service) {
- newSvc.Spec.Selector = map[string]string{"newkey": "newvalue"}
- },
- expectErr: false,
- },
- }
- for _, tc := range testCases {
- oldSvc := makeValidService()
- newSvc := makeValidService()
- tc.tweakSvc(&oldSvc, &newSvc)
- ctx := genericapirequest.NewDefaultContext()
- err := rest.BeforeUpdate(Strategy, ctx, runtime.Object(&oldSvc), runtime.Object(&newSvc))
- if tc.expectErr && err == nil {
- t.Errorf("unexpected non-error for %q", tc.name)
- }
- if !tc.expectErr && err != nil {
- t.Errorf("unexpected error for %q: %v", tc.name, err)
- }
- }
- }
- func TestServiceStatusStrategy(t *testing.T) {
- ctx := genericapirequest.NewDefaultContext()
- if !StatusStrategy.NamespaceScoped() {
- t.Errorf("Service must be namespace scoped")
- }
- oldService := makeValidService()
- newService := makeValidService()
- oldService.ResourceVersion = "4"
- newService.ResourceVersion = "4"
- newService.Spec.SessionAffinity = "ClientIP"
- newService.Status = api.ServiceStatus{
- LoadBalancer: api.LoadBalancerStatus{
- Ingress: []api.LoadBalancerIngress{
- {IP: "127.0.0.2"},
- },
- },
- }
- StatusStrategy.PrepareForUpdate(ctx, &newService, &oldService)
- if newService.Status.LoadBalancer.Ingress[0].IP != "127.0.0.2" {
- t.Errorf("Service status updates should allow change of status fields")
- }
- if newService.Spec.SessionAffinity != "None" {
- t.Errorf("PrepareForUpdate should have preserved old spec")
- }
- errs := StatusStrategy.ValidateUpdate(ctx, &newService, &oldService)
- if len(errs) != 0 {
- t.Errorf("Unexpected error %v", errs)
- }
- }
- func makeServiceWithIPFamily(IPFamily *api.IPFamily) *api.Service {
- return &api.Service{
- Spec: api.ServiceSpec{
- IPFamily: IPFamily,
- },
- }
- }
- func TestDropDisabledField(t *testing.T) {
- ipv4Service := api.IPv4Protocol
- ipv6Service := api.IPv6Protocol
- testCases := []struct {
- name string
- enableDualStack bool
- svc *api.Service
- oldSvc *api.Service
- compareSvc *api.Service
- }{
- {
- name: "not dual stack, field not used",
- enableDualStack: false,
- svc: makeServiceWithIPFamily(nil),
- oldSvc: nil,
- compareSvc: makeServiceWithIPFamily(nil),
- },
- {
- name: "not dual stack, field used in new, not in old",
- enableDualStack: false,
- svc: makeServiceWithIPFamily(&ipv4Service),
- oldSvc: nil,
- compareSvc: makeServiceWithIPFamily(nil),
- },
- {
- name: "not dual stack, field used in old and new",
- enableDualStack: false,
- svc: makeServiceWithIPFamily(&ipv4Service),
- oldSvc: makeServiceWithIPFamily(&ipv4Service),
- compareSvc: makeServiceWithIPFamily(&ipv4Service),
- },
- {
- name: "dualstack, field used",
- enableDualStack: true,
- svc: makeServiceWithIPFamily(&ipv6Service),
- oldSvc: nil,
- compareSvc: makeServiceWithIPFamily(&ipv6Service),
- },
- /* add more tests for other dropped fields as needed */
- }
- for _, tc := range testCases {
- func() {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
- old := tc.oldSvc.DeepCopy()
- dropServiceDisabledFields(tc.svc, tc.oldSvc)
- // old node should never be changed
- if !reflect.DeepEqual(tc.oldSvc, old) {
- t.Errorf("%v: old svc changed: %v", tc.name, diff.ObjectReflectDiff(tc.oldSvc, old))
- }
- if !reflect.DeepEqual(tc.svc, tc.compareSvc) {
- t.Errorf("%v: unexpected svc spec: %v", tc.name, diff.ObjectReflectDiff(tc.svc, tc.compareSvc))
- }
- }()
- }
- }
|