func init() { mkintp := func(i int64) *int64 { return &i } err := api.Scheme.AddDefaultingFuncs( func(obj *DeploymentConfigSpec) { if obj.Triggers == nil { obj.Triggers = []DeploymentTriggerPolicy{ {Type: DeploymentTriggerOnConfigChange}, } } }, func(obj *DeploymentStrategy) { if len(obj.Type) == 0 { obj.Type = DeploymentStrategyTypeRolling } if obj.Type == DeploymentStrategyTypeRolling && obj.RollingParams == nil { obj.RollingParams = &RollingDeploymentStrategyParams{ IntervalSeconds: mkintp(deployapi.DefaultRollingIntervalSeconds), UpdatePeriodSeconds: mkintp(deployapi.DefaultRollingUpdatePeriodSeconds), TimeoutSeconds: mkintp(deployapi.DefaultRollingTimeoutSeconds), } } }, func(obj *RollingDeploymentStrategyParams) { if obj.IntervalSeconds == nil { obj.IntervalSeconds = mkintp(deployapi.DefaultRollingIntervalSeconds) } if obj.UpdatePeriodSeconds == nil { obj.UpdatePeriodSeconds = mkintp(deployapi.DefaultRollingUpdatePeriodSeconds) } if obj.TimeoutSeconds == nil { obj.TimeoutSeconds = mkintp(deployapi.DefaultRollingTimeoutSeconds) } if obj.UpdatePercent == nil { // Apply defaults. if obj.MaxUnavailable == nil { maxUnavailable := kutil.NewIntOrStringFromString("25%") obj.MaxUnavailable = &maxUnavailable } if obj.MaxSurge == nil { maxSurge := kutil.NewIntOrStringFromString("25%") obj.MaxSurge = &maxSurge } } }, ) if err != nil { panic(err) } }
func TestGetURLParts(t *testing.T) { testCases := []struct { probe *api.HTTPGetAction ok bool host string port int path string }{ {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromInt(-1), Path: ""}, false, "", -1, ""}, {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString(""), Path: ""}, false, "", -1, ""}, {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString("-1"), Path: ""}, false, "", -1, ""}, {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString("not-found"), Path: ""}, false, "", -1, ""}, {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString("found"), Path: ""}, true, "127.0.0.1", 93, ""}, {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromInt(76), Path: ""}, true, "127.0.0.1", 76, ""}, {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString("118"), Path: ""}, true, "127.0.0.1", 118, ""}, {&api.HTTPGetAction{Host: "hostname", Port: util.NewIntOrStringFromInt(76), Path: "path"}, true, "hostname", 76, "path"}, } for _, test := range testCases { state := api.PodStatus{PodIP: "127.0.0.1"} container := api.Container{ Ports: []api.ContainerPort{{Name: "found", ContainerPort: 93}}, LivenessProbe: &api.Probe{ Handler: api.Handler{ HTTPGet: test.probe, }, }, } scheme := test.probe.Scheme if scheme == "" { scheme = api.URISchemeHTTP } host := test.probe.Host if host == "" { host = state.PodIP } port, err := extractPort(test.probe.Port, container) if test.ok && err != nil { t.Errorf("Unexpected error: %v", err) } path := test.probe.Path if !test.ok && err == nil { t.Errorf("Expected error for %+v, got %s%s:%d/%s", test, scheme, host, port, path) } if test.ok { if host != test.host || port != test.port || path != test.path { t.Errorf("Expected %s:%d/%s, got %s:%d/%s", test.host, test.port, test.path, host, port, path) } } } }
func convert_api_RollingDeploymentStrategyParams_To_v1_RollingDeploymentStrategyParams(in *newer.RollingDeploymentStrategyParams, out *RollingDeploymentStrategyParams, s conversion.Scope) error { out.UpdatePeriodSeconds = in.UpdatePeriodSeconds out.IntervalSeconds = in.IntervalSeconds out.TimeoutSeconds = in.TimeoutSeconds out.UpdatePercent = in.UpdatePercent if out.MaxUnavailable == nil { out.MaxUnavailable = &kutil.IntOrString{} } if out.MaxSurge == nil { out.MaxSurge = &kutil.IntOrString{} } if in.UpdatePercent != nil { pct := kutil.NewIntOrStringFromString(fmt.Sprintf("%d%%", int(math.Abs(float64(*in.UpdatePercent))))) if *in.UpdatePercent > 0 { out.MaxSurge = &pct } else { out.MaxUnavailable = &pct } } else { if err := s.Convert(&in.MaxUnavailable, out.MaxUnavailable, 0); err != nil { return err } if err := s.Convert(&in.MaxSurge, out.MaxSurge, 0); err != nil { return err } } return nil }
// Generate accepts a set of parameters and maps them into a new route func (RouteGenerator) Generate(genericParams map[string]interface{}) (runtime.Object, error) { var ( labels map[string]string err error ) params := map[string]string{} for key, value := range genericParams { strVal, isString := value.(string) if !isString { return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key) } params[key] = strVal } labelString, found := params["labels"] if found && len(labelString) > 0 { labels, err = kubectl.ParseLabels(labelString) if err != nil { return nil, err } } name, found := params["name"] if !found || len(name) == 0 { name, found = params["default-name"] if !found || len(name) == 0 { return nil, fmt.Errorf("'name' is a required parameter.") } } var portString string portString, found = params["port"] if !found || len(portString) == 0 { portString = strings.Split(params["ports"], ",")[0] } if len(portString) == 0 { return nil, fmt.Errorf("exposed service does not have any target ports specified") } return &api.Route{ ObjectMeta: kapi.ObjectMeta{ Name: name, Labels: labels, }, Spec: api.RouteSpec{ Host: params["hostname"], To: kapi.ObjectReference{ Name: params["default-name"], }, Port: &api.RoutePort{ TargetPort: util.NewIntOrStringFromString(portString), }, }, }, nil }
func TestSetDefaultServicePort(t *testing.T) { // Unchanged if set. in := &versioned.Service{Spec: versioned.ServiceSpec{ Ports: []versioned.ServicePort{ {Protocol: "UDP", Port: 9376, TargetPort: util.NewIntOrStringFromString("p")}, {Protocol: "UDP", Port: 8675, TargetPort: util.NewIntOrStringFromInt(309)}, }, }} out := roundTrip(t, runtime.Object(in)).(*versioned.Service) if out.Spec.Ports[0].Protocol != versioned.ProtocolUDP { t.Errorf("Expected protocol %s, got %s", versioned.ProtocolUDP, out.Spec.Ports[0].Protocol) } if out.Spec.Ports[0].TargetPort != util.NewIntOrStringFromString("p") { t.Errorf("Expected port %v, got %v", in.Spec.Ports[0].Port, out.Spec.Ports[0].TargetPort) } if out.Spec.Ports[1].Protocol != versioned.ProtocolUDP { t.Errorf("Expected protocol %s, got %s", versioned.ProtocolUDP, out.Spec.Ports[1].Protocol) } if out.Spec.Ports[1].TargetPort != util.NewIntOrStringFromInt(309) { t.Errorf("Expected port %v, got %v", in.Spec.Ports[1].Port, out.Spec.Ports[1].TargetPort) } // Defaulted. in = &versioned.Service{Spec: versioned.ServiceSpec{ Ports: []versioned.ServicePort{ {Protocol: "", Port: 9376, TargetPort: util.NewIntOrStringFromString("")}, {Protocol: "", Port: 8675, TargetPort: util.NewIntOrStringFromInt(0)}, }, }} out = roundTrip(t, runtime.Object(in)).(*versioned.Service) if out.Spec.Ports[0].Protocol != versioned.ProtocolTCP { t.Errorf("Expected protocol %s, got %s", versioned.ProtocolTCP, out.Spec.Ports[0].Protocol) } if out.Spec.Ports[0].TargetPort != util.NewIntOrStringFromInt(in.Spec.Ports[0].Port) { t.Errorf("Expected port %v, got %v", in.Spec.Ports[0].Port, out.Spec.Ports[0].TargetPort) } if out.Spec.Ports[1].Protocol != versioned.ProtocolTCP { t.Errorf("Expected protocol %s, got %s", versioned.ProtocolTCP, out.Spec.Ports[1].Protocol) } if out.Spec.Ports[1].TargetPort != util.NewIntOrStringFromInt(in.Spec.Ports[1].Port) { t.Errorf("Expected port %v, got %v", in.Spec.Ports[1].Port, out.Spec.Ports[1].TargetPort) } }
func TestUpdate_assignOriginalAnnotation(t *testing.T) { oldRc := oldRc(1, 1) delete(oldRc.Annotations, originalReplicasAnnotation) newRc := newRc(1, 1) var updatedOldRc *api.ReplicationController fake := &testclient.Fake{} fake.AddReactor("*", "*", func(action testclient.Action) (handled bool, ret runtime.Object, err error) { switch a := action.(type) { case testclient.GetAction: return true, oldRc, nil case testclient.UpdateAction: updatedOldRc = a.GetObject().(*api.ReplicationController) return true, updatedOldRc, nil } return false, nil, nil }) updater := &RollingUpdater{ c: fake, ns: "default", scaleAndWait: func(rc *api.ReplicationController, retry *RetryParams, wait *RetryParams) (*api.ReplicationController, error) { return rc, nil }, getOrCreateTargetController: func(controller *api.ReplicationController, sourceId string) (*api.ReplicationController, bool, error) { return newRc, false, nil }, cleanup: func(oldRc, newRc *api.ReplicationController, config *RollingUpdaterConfig) error { return nil }, waitForReadyPods: func(interval, timeout time.Duration, oldRc, newRc *api.ReplicationController) (int, int, error) { return 1, 1, nil }, } var buffer bytes.Buffer config := &RollingUpdaterConfig{ Out: &buffer, OldRc: oldRc, NewRc: newRc, UpdatePeriod: 0, Interval: time.Millisecond, Timeout: time.Millisecond, CleanupPolicy: DeleteRollingUpdateCleanupPolicy, MaxUnavailable: util.NewIntOrStringFromString("100%"), } err := updater.Update(config) if err != nil { t.Fatalf("unexpected error: %v", err) } if updatedOldRc == nil { t.Fatalf("expected rc to be updated") } if e, a := "1", updatedOldRc.Annotations[originalReplicasAnnotation]; e != a { t.Fatalf("expected annotation value %s, got %s", e, a) } }
func TestGetTCPAddrParts(t *testing.T) { testCases := []struct { probe *api.TCPSocketAction ok bool host string port int }{ {&api.TCPSocketAction{Port: util.NewIntOrStringFromInt(-1)}, false, "", -1}, {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("")}, false, "", -1}, {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("-1")}, false, "", -1}, {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("not-found")}, false, "", -1}, {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("found")}, true, "1.2.3.4", 93}, {&api.TCPSocketAction{Port: util.NewIntOrStringFromInt(76)}, true, "1.2.3.4", 76}, {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("118")}, true, "1.2.3.4", 118}, } for _, test := range testCases { host := "1.2.3.4" container := api.Container{ Ports: []api.ContainerPort{{Name: "found", ContainerPort: 93}}, LivenessProbe: &api.Probe{ Handler: api.Handler{ TCPSocket: test.probe, }, }, } port, err := extractPort(test.probe.Port, container) if !test.ok && err == nil { t.Errorf("Expected error for %+v, got %s:%d", test, host, port) } if test.ok && err != nil { t.Errorf("Unexpected error: %v", err) } if test.ok { if host != test.host || port != test.port { t.Errorf("Expected %s:%d, got %s:%d", test.host, test.port, host, port) } } } }
func TestGetEndpoints(t *testing.T) { // 2 pods each of which have 3 targetPorts exposed via a single service endpointAddresses := []api.EndpointAddress{ {IP: "1.2.3.4"}, {IP: "6.7.8.9"}, } ports := []int{80, 443, 3306} endpointPorts := []api.EndpointPort{ {Port: ports[0], Protocol: "TCP"}, {Port: ports[1], Protocol: "TCP"}, {Port: ports[2], Protocol: "TCP", Name: "mysql"}, } servicePorts := []api.ServicePort{ {Port: 10, TargetPort: util.NewIntOrStringFromInt(ports[0])}, {Port: 20, TargetPort: util.NewIntOrStringFromInt(ports[1])}, {Port: 30, TargetPort: util.NewIntOrStringFromString("mysql")}, } svc := getService(servicePorts) endpoints := []*api.Endpoints{getEndpoints(svc, endpointAddresses, endpointPorts)} flb := newFakeLoadBalancerController(endpoints, []*api.Service{svc}) for i := range ports { eps := flb.getEndpoints(svc, &svc.Spec.Ports[i]) expectedEps := sets.NewString() for _, address := range endpointAddresses { expectedEps.Insert(fmt.Sprintf("%v:%v", address.IP, ports[i])) } receivedEps := sets.NewString() for _, ep := range eps { receivedEps.Insert(ep) } if len(receivedEps) != len(expectedEps) || !expectedEps.IsSuperset(receivedEps) { t.Fatalf("Unexpected endpoints, received %+v, expected %+v", receivedEps, expectedEps) } glog.Infof("Got endpoints %+v", receivedEps) } }
func TestGenerateService(t *testing.T) { tests := []struct { generator Generator params map[string]string expected api.Service }{ { generator: ServiceGeneratorV2{}, params: map[string]string{ "selector": "foo=bar,baz=blah", "name": "test", "port": "80", "protocol": "TCP", "container-port": "1234", }, expected: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "test", }, Spec: api.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, Ports: []api.ServicePort{ { Port: 80, Protocol: "TCP", TargetPort: util.NewIntOrStringFromInt(1234), }, }, }, }, }, { generator: ServiceGeneratorV2{}, params: map[string]string{ "selector": "foo=bar,baz=blah", "name": "test", "port": "80", "protocol": "UDP", "container-port": "foobar", }, expected: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "test", }, Spec: api.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, Ports: []api.ServicePort{ { Port: 80, Protocol: "UDP", TargetPort: util.NewIntOrStringFromString("foobar"), }, }, }, }, }, { generator: ServiceGeneratorV2{}, params: map[string]string{ "selector": "foo=bar,baz=blah", "labels": "key1=value1,key2=value2", "name": "test", "port": "80", "protocol": "TCP", "container-port": "1234", }, expected: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "test", Labels: map[string]string{ "key1": "value1", "key2": "value2", }, }, Spec: api.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, Ports: []api.ServicePort{ { Port: 80, Protocol: "TCP", TargetPort: util.NewIntOrStringFromInt(1234), }, }, }, }, }, { generator: ServiceGeneratorV2{}, params: map[string]string{ "selector": "foo=bar,baz=blah", "name": "test", "port": "80", "protocol": "UDP", "container-port": "foobar", "public-ip": "1.2.3.4", }, expected: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "test", }, Spec: api.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, Ports: []api.ServicePort{ { Port: 80, Protocol: "UDP", TargetPort: util.NewIntOrStringFromString("foobar"), }, }, DeprecatedPublicIPs: []string{"1.2.3.4"}, }, }, }, { generator: ServiceGeneratorV2{}, params: map[string]string{ "selector": "foo=bar,baz=blah", "name": "test", "port": "80", "protocol": "UDP", "container-port": "foobar", "public-ip": "1.2.3.4", "create-external-load-balancer": "true", }, expected: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "test", }, Spec: api.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, Ports: []api.ServicePort{ { Port: 80, Protocol: "UDP", TargetPort: util.NewIntOrStringFromString("foobar"), }, }, Type: api.ServiceTypeLoadBalancer, DeprecatedPublicIPs: []string{"1.2.3.4"}, }, }, }, { generator: ServiceGeneratorV2{}, params: map[string]string{ "selector": "foo=bar,baz=blah", "name": "test", "port": "80", "protocol": "UDP", "container-port": "foobar", "type": string(api.ServiceTypeNodePort), }, expected: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "test", }, Spec: api.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, Ports: []api.ServicePort{ { Port: 80, Protocol: "UDP", TargetPort: util.NewIntOrStringFromString("foobar"), }, }, Type: api.ServiceTypeNodePort, }, }, }, { generator: ServiceGeneratorV2{}, params: map[string]string{ "selector": "foo=bar,baz=blah", "name": "test", "port": "80", "protocol": "UDP", "container-port": "foobar", "create-external-load-balancer": "true", // ignored when type is present "type": string(api.ServiceTypeNodePort), }, expected: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "test", }, Spec: api.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, Ports: []api.ServicePort{ { Port: 80, Protocol: "UDP", TargetPort: util.NewIntOrStringFromString("foobar"), }, }, Type: api.ServiceTypeNodePort, }, }, }, { generator: ServiceGeneratorV1{}, params: map[string]string{ "selector": "foo=bar,baz=blah", "name": "test", "port": "80", "protocol": "TCP", "container-port": "1234", }, expected: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "test", }, Spec: api.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, Ports: []api.ServicePort{ { Name: "default", Port: 80, Protocol: "TCP", TargetPort: util.NewIntOrStringFromInt(1234), }, }, }, }, }, { generator: ServiceGeneratorV1{}, params: map[string]string{ "selector": "foo=bar,baz=blah", "name": "test", "port": "80", "protocol": "TCP", "container-port": "1234", "session-affinity": "ClientIP", }, expected: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "test", }, Spec: api.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, Ports: []api.ServicePort{ { Name: "default", Port: 80, Protocol: "TCP", TargetPort: util.NewIntOrStringFromInt(1234), }, }, SessionAffinity: api.ServiceAffinityClientIP, }, }, }, } for _, test := range tests { obj, err := test.generator.Generate(test.params) if !reflect.DeepEqual(obj, &test.expected) { t.Errorf("expected:\n%#v\ngot\n%#v\n", &test.expected, obj) } if err != nil { t.Errorf("unexpected error: %v", err) } } }
// TestUpdate performs complex scenario testing for rolling updates. It // provides fine grained control over the states for each update interval to // allow the expression of as many edge cases as possible. func TestUpdate(t *testing.T) { // up represents a simulated scale up event and expectation type up struct { // to is the expected replica count for a scale-up to int } // down represents a simulated scale down event and expectation type down struct { // oldReady is the number of oldRc replicas which will be seen // as ready during the scale down attempt oldReady int // newReady is the number of newRc replicas which will be seen // as ready during the scale up attempt newReady int // to is the expected replica count for the scale down to int // noop and to are mutually exclusive; if noop is true, that means for // this down event, no scaling attempt should be made (for example, if // by scaling down, the readiness minimum would be crossed.) noop bool } tests := []struct { name string // oldRc is the "from" deployment oldRc *api.ReplicationController // newRc is the "to" deployment newRc *api.ReplicationController // whether newRc existed (false means it was created) newRcExists bool maxUnavail util.IntOrString maxSurge util.IntOrString // expected is the sequence of up/down events that will be simulated and // verified expected []interface{} // output is the expected textual output written output string }{ { name: "10->10 30/0 fast readiness", oldRc: oldRc(10, 10), newRc: newRc(0, 10), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("30%"), maxSurge: util.NewIntOrStringFromString("0%"), expected: []interface{}{ down{oldReady: 10, newReady: 0, to: 7}, up{3}, down{oldReady: 7, newReady: 3, to: 4}, up{6}, down{oldReady: 4, newReady: 6, to: 1}, up{9}, down{oldReady: 1, newReady: 9, to: 0}, up{10}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 7 pods available, don't exceed 10 pods) Scaling foo-v1 down to 7 Scaling foo-v2 up to 3 Scaling foo-v1 down to 4 Scaling foo-v2 up to 6 Scaling foo-v1 down to 1 Scaling foo-v2 up to 9 Scaling foo-v1 down to 0 Scaling foo-v2 up to 10 `, }, { name: "10->10 30/0 delayed readiness", oldRc: oldRc(10, 10), newRc: newRc(0, 10), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("30%"), maxSurge: util.NewIntOrStringFromString("0%"), expected: []interface{}{ down{oldReady: 10, newReady: 0, to: 7}, up{3}, down{oldReady: 7, newReady: 0, noop: true}, down{oldReady: 7, newReady: 1, to: 6}, up{4}, down{oldReady: 6, newReady: 4, to: 3}, up{7}, down{oldReady: 3, newReady: 7, to: 0}, up{10}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 7 pods available, don't exceed 10 pods) Scaling foo-v1 down to 7 Scaling foo-v2 up to 3 Scaling foo-v1 down to 6 Scaling foo-v2 up to 4 Scaling foo-v1 down to 3 Scaling foo-v2 up to 7 Scaling foo-v1 down to 0 Scaling foo-v2 up to 10 `, }, { name: "10->10 30/0 fast readiness, continuation", oldRc: oldRc(7, 10), newRc: newRc(3, 10), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("30%"), maxSurge: util.NewIntOrStringFromString("0%"), expected: []interface{}{ down{oldReady: 7, newReady: 3, to: 4}, up{6}, down{oldReady: 4, newReady: 6, to: 1}, up{9}, down{oldReady: 1, newReady: 9, to: 0}, up{10}, }, output: `Created foo-v2 Scaling up foo-v2 from 3 to 10, scaling down foo-v1 from 7 to 0 (keep 7 pods available, don't exceed 10 pods) Scaling foo-v1 down to 4 Scaling foo-v2 up to 6 Scaling foo-v1 down to 1 Scaling foo-v2 up to 9 Scaling foo-v1 down to 0 Scaling foo-v2 up to 10 `, }, { name: "10->10 30/0 fast readiness, continued after restart which prevented first scale-up", oldRc: oldRc(7, 10), newRc: newRc(0, 10), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("30%"), maxSurge: util.NewIntOrStringFromString("0%"), expected: []interface{}{ down{oldReady: 7, newReady: 0, noop: true}, up{3}, down{oldReady: 7, newReady: 3, to: 4}, up{6}, down{oldReady: 4, newReady: 6, to: 1}, up{9}, down{oldReady: 1, newReady: 9, to: 0}, up{10}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 7 to 0 (keep 7 pods available, don't exceed 10 pods) Scaling foo-v2 up to 3 Scaling foo-v1 down to 4 Scaling foo-v2 up to 6 Scaling foo-v1 down to 1 Scaling foo-v2 up to 9 Scaling foo-v1 down to 0 Scaling foo-v2 up to 10 `, }, { name: "10->10 0/30 fast readiness", oldRc: oldRc(10, 10), newRc: newRc(0, 10), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("0%"), maxSurge: util.NewIntOrStringFromString("30%"), expected: []interface{}{ up{3}, down{oldReady: 10, newReady: 3, to: 7}, up{6}, down{oldReady: 7, newReady: 6, to: 4}, up{9}, down{oldReady: 4, newReady: 9, to: 1}, up{10}, down{oldReady: 1, newReady: 10, to: 0}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 10 pods available, don't exceed 13 pods) Scaling foo-v2 up to 3 Scaling foo-v1 down to 7 Scaling foo-v2 up to 6 Scaling foo-v1 down to 4 Scaling foo-v2 up to 9 Scaling foo-v1 down to 1 Scaling foo-v2 up to 10 Scaling foo-v1 down to 0 `, }, { name: "10->10 0/30 delayed readiness", oldRc: oldRc(10, 10), newRc: newRc(0, 10), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("0%"), maxSurge: util.NewIntOrStringFromString("30%"), expected: []interface{}{ up{3}, down{oldReady: 10, newReady: 0, noop: true}, down{oldReady: 10, newReady: 1, to: 9}, up{4}, down{oldReady: 9, newReady: 3, to: 7}, up{6}, down{oldReady: 7, newReady: 6, to: 4}, up{9}, down{oldReady: 4, newReady: 9, to: 1}, up{10}, down{oldReady: 1, newReady: 9, noop: true}, down{oldReady: 1, newReady: 10, to: 0}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 10 pods available, don't exceed 13 pods) Scaling foo-v2 up to 3 Scaling foo-v1 down to 9 Scaling foo-v2 up to 4 Scaling foo-v1 down to 7 Scaling foo-v2 up to 6 Scaling foo-v1 down to 4 Scaling foo-v2 up to 9 Scaling foo-v1 down to 1 Scaling foo-v2 up to 10 Scaling foo-v1 down to 0 `, }, { name: "10->10 10/20 fast readiness", oldRc: oldRc(10, 10), newRc: newRc(0, 10), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("10%"), maxSurge: util.NewIntOrStringFromString("20%"), expected: []interface{}{ up{2}, down{oldReady: 10, newReady: 2, to: 7}, up{5}, down{oldReady: 7, newReady: 5, to: 4}, up{8}, down{oldReady: 4, newReady: 8, to: 1}, up{10}, down{oldReady: 10, newReady: 1, to: 0}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 9 pods available, don't exceed 12 pods) Scaling foo-v2 up to 2 Scaling foo-v1 down to 7 Scaling foo-v2 up to 5 Scaling foo-v1 down to 4 Scaling foo-v2 up to 8 Scaling foo-v1 down to 1 Scaling foo-v2 up to 10 Scaling foo-v1 down to 0 `, }, { name: "10->10 10/20 delayed readiness", oldRc: oldRc(10, 10), newRc: newRc(0, 10), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("10%"), maxSurge: util.NewIntOrStringFromString("20%"), expected: []interface{}{ up{2}, down{oldReady: 10, newReady: 2, to: 7}, up{5}, down{oldReady: 7, newReady: 4, to: 5}, up{7}, down{oldReady: 5, newReady: 4, noop: true}, down{oldReady: 5, newReady: 7, to: 2}, up{10}, down{oldReady: 2, newReady: 9, to: 0}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 9 pods available, don't exceed 12 pods) Scaling foo-v2 up to 2 Scaling foo-v1 down to 7 Scaling foo-v2 up to 5 Scaling foo-v1 down to 5 Scaling foo-v2 up to 7 Scaling foo-v1 down to 2 Scaling foo-v2 up to 10 Scaling foo-v1 down to 0 `, }, { name: "10->10 10/20 fast readiness continued after restart which prevented first scale-down", oldRc: oldRc(10, 10), newRc: newRc(2, 10), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("10%"), maxSurge: util.NewIntOrStringFromString("20%"), expected: []interface{}{ down{oldReady: 10, newReady: 2, to: 7}, up{5}, down{oldReady: 7, newReady: 5, to: 4}, up{8}, down{oldReady: 4, newReady: 8, to: 1}, up{10}, down{oldReady: 1, newReady: 10, to: 0}, }, output: `Created foo-v2 Scaling up foo-v2 from 2 to 10, scaling down foo-v1 from 10 to 0 (keep 9 pods available, don't exceed 12 pods) Scaling foo-v1 down to 7 Scaling foo-v2 up to 5 Scaling foo-v1 down to 4 Scaling foo-v2 up to 8 Scaling foo-v1 down to 1 Scaling foo-v2 up to 10 Scaling foo-v1 down to 0 `, }, { name: "10->10 0/100 fast readiness", oldRc: oldRc(10, 10), newRc: newRc(0, 10), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("0%"), maxSurge: util.NewIntOrStringFromString("100%"), expected: []interface{}{ up{10}, down{oldReady: 10, newReady: 10, to: 0}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 10 pods available, don't exceed 20 pods) Scaling foo-v2 up to 10 Scaling foo-v1 down to 0 `, }, { name: "10->10 0/100 delayed readiness", oldRc: oldRc(10, 10), newRc: newRc(0, 10), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("0%"), maxSurge: util.NewIntOrStringFromString("100%"), expected: []interface{}{ up{10}, down{oldReady: 10, newReady: 0, noop: true}, down{oldReady: 10, newReady: 2, to: 8}, down{oldReady: 8, newReady: 7, to: 3}, down{oldReady: 3, newReady: 10, to: 0}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 10 pods available, don't exceed 20 pods) Scaling foo-v2 up to 10 Scaling foo-v1 down to 8 Scaling foo-v1 down to 3 Scaling foo-v1 down to 0 `, }, { name: "10->10 100/0 fast readiness", oldRc: oldRc(10, 10), newRc: newRc(0, 10), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("100%"), maxSurge: util.NewIntOrStringFromString("0%"), expected: []interface{}{ down{oldReady: 10, newReady: 0, to: 0}, up{10}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 0 pods available, don't exceed 10 pods) Scaling foo-v1 down to 0 Scaling foo-v2 up to 10 `, }, { name: "1->1 10/0 fast readiness", oldRc: oldRc(1, 1), newRc: newRc(0, 1), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("10%"), maxSurge: util.NewIntOrStringFromString("0%"), expected: []interface{}{ down{oldReady: 1, newReady: 0, to: 0}, up{1}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 0 pods available, don't exceed 1 pods) Scaling foo-v1 down to 0 Scaling foo-v2 up to 1 `, }, { name: "1->1 0/10 delayed readiness", oldRc: oldRc(1, 1), newRc: newRc(0, 1), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("0%"), maxSurge: util.NewIntOrStringFromString("10%"), expected: []interface{}{ up{1}, down{oldReady: 1, newReady: 0, noop: true}, down{oldReady: 1, newReady: 1, to: 0}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 1 pods available, don't exceed 2 pods) Scaling foo-v2 up to 1 Scaling foo-v1 down to 0 `, }, { name: "1->1 10/10 delayed readiness", oldRc: oldRc(1, 1), newRc: newRc(0, 1), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("10%"), maxSurge: util.NewIntOrStringFromString("10%"), expected: []interface{}{ up{1}, down{oldReady: 1, newReady: 0, noop: true}, down{oldReady: 1, newReady: 1, to: 0}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 0 pods available, don't exceed 2 pods) Scaling foo-v2 up to 1 Scaling foo-v1 down to 0 `, }, { name: "3->3 1/1 fast readiness (absolute values)", oldRc: oldRc(3, 3), newRc: newRc(0, 3), newRcExists: false, maxUnavail: util.NewIntOrStringFromInt(0), maxSurge: util.NewIntOrStringFromInt(1), expected: []interface{}{ up{1}, down{oldReady: 3, newReady: 1, to: 2}, up{2}, down{oldReady: 2, newReady: 2, to: 1}, up{3}, down{oldReady: 1, newReady: 3, to: 0}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 3, scaling down foo-v1 from 3 to 0 (keep 3 pods available, don't exceed 4 pods) Scaling foo-v2 up to 1 Scaling foo-v1 down to 2 Scaling foo-v2 up to 2 Scaling foo-v1 down to 1 Scaling foo-v2 up to 3 Scaling foo-v1 down to 0 `, }, { name: "10->10 0/20 fast readiness, continued after restart which resulted in partial first scale-up", oldRc: oldRc(6, 10), newRc: newRc(5, 10), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("0%"), maxSurge: util.NewIntOrStringFromString("20%"), expected: []interface{}{ up{6}, down{oldReady: 6, newReady: 6, to: 4}, up{8}, down{oldReady: 4, newReady: 8, to: 2}, up{10}, down{oldReady: 10, newReady: 2, to: 0}, }, output: `Created foo-v2 Scaling up foo-v2 from 5 to 10, scaling down foo-v1 from 6 to 0 (keep 10 pods available, don't exceed 12 pods) Scaling foo-v2 up to 6 Scaling foo-v1 down to 4 Scaling foo-v2 up to 8 Scaling foo-v1 down to 2 Scaling foo-v2 up to 10 Scaling foo-v1 down to 0 `, }, { name: "10->20 0/300 fast readiness", oldRc: oldRc(10, 10), newRc: newRc(0, 20), newRcExists: false, maxUnavail: util.NewIntOrStringFromString("0%"), maxSurge: util.NewIntOrStringFromString("300%"), expected: []interface{}{ up{20}, down{oldReady: 10, newReady: 20, to: 0}, }, output: `Created foo-v2 Scaling up foo-v2 from 0 to 20, scaling down foo-v1 from 10 to 0 (keep 10 pods available, don't exceed 70 pods) Scaling foo-v2 up to 20 Scaling foo-v1 down to 0 `, }, } for i, test := range tests { // Extract expectations into some makeshift FIFOs so they can be returned // in the correct order from the right places. This lets scale downs be // expressed a single event even though the data is used from multiple // interface calls. oldReady := []int{} newReady := []int{} upTo := []int{} downTo := []int{} for _, event := range test.expected { switch e := event.(type) { case down: oldReady = append(oldReady, e.oldReady) newReady = append(newReady, e.newReady) if !e.noop { downTo = append(downTo, e.to) } case up: upTo = append(upTo, e.to) } } // Make a way to get the next item from our FIFOs. Returns -1 if the array // is empty. next := func(s *[]int) int { slice := *s v := -1 if len(slice) > 0 { v = slice[0] if len(slice) > 1 { *s = slice[1:] } else { *s = []int{} } } return v } t.Logf("running test %d (%s) (up: %v, down: %v, oldReady: %v, newReady: %v)", i, test.name, upTo, downTo, oldReady, newReady) updater := &RollingUpdater{ ns: "default", scaleAndWait: func(rc *api.ReplicationController, retry *RetryParams, wait *RetryParams) (*api.ReplicationController, error) { // Return a scale up or scale down expectation depending on the rc, // and throw errors if there is no expectation expressed for this // call. expected := -1 switch { case rc == test.newRc: t.Logf("scaling up %s:%d", rc.Name, rc.Spec.Replicas) expected = next(&upTo) case rc == test.oldRc: t.Logf("scaling down %s:%d", rc.Name, rc.Spec.Replicas) expected = next(&downTo) } if expected == -1 { t.Fatalf("unexpected scale of %s to %d", rc.Name, rc.Spec.Replicas) } else if e, a := expected, rc.Spec.Replicas; e != a { t.Fatalf("expected scale of %s to %d, got %d", rc.Name, e, a) } // Simulate the scale. rc.Status.Replicas = rc.Spec.Replicas return rc, nil }, getOrCreateTargetController: func(controller *api.ReplicationController, sourceId string) (*api.ReplicationController, bool, error) { // Simulate a create vs. update of an existing controller. return test.newRc, test.newRcExists, nil }, cleanup: func(oldRc, newRc *api.ReplicationController, config *RollingUpdaterConfig) error { return nil }, } // Set up a mock readiness check which handles the test assertions. updater.waitForReadyPods = func(interval, timeout time.Duration, oldRc, newRc *api.ReplicationController) (int, int, error) { // Return simulated readiness, and throw an error if this call has no // expectations defined. oldReady := next(&oldReady) newReady := next(&newReady) if oldReady == -1 || newReady == -1 { t.Fatalf("unexpected waitForReadyPods call for:\noldRc: %+v\nnewRc: %+v", oldRc, newRc) } return oldReady, newReady, nil } var buffer bytes.Buffer config := &RollingUpdaterConfig{ Out: &buffer, OldRc: test.oldRc, NewRc: test.newRc, UpdatePeriod: 0, Interval: time.Millisecond, Timeout: time.Millisecond, CleanupPolicy: DeleteRollingUpdateCleanupPolicy, MaxUnavailable: test.maxUnavail, MaxSurge: test.maxSurge, } err := updater.Update(config) if err != nil { t.Errorf("unexpected error: %v", err) } if buffer.String() != test.output { t.Errorf("Bad output. expected:\n%s\ngot:\n%s", test.output, buffer.String()) } } }
// FuzzerFor can randomly populate api objects that are destined for version. func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer { f := fuzz.New().NilChance(.5).NumElements(1, 1) if src != nil { f.RandSource(src) } f.Funcs( func(j *runtime.PluginBase, c fuzz.Continue) { // Do nothing; this struct has only a Kind field and it must stay blank in memory. }, func(j *runtime.TypeMeta, c fuzz.Continue) { // We have to customize the randomization of TypeMetas because their // APIVersion and Kind must remain blank in memory. j.APIVersion = "" j.Kind = "" }, func(j *api.TypeMeta, c fuzz.Continue) { // We have to customize the randomization of TypeMetas because their // APIVersion and Kind must remain blank in memory. j.APIVersion = "" j.Kind = "" }, func(j *api.ObjectMeta, c fuzz.Continue) { j.Name = c.RandString() j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) j.SelfLink = c.RandString() j.UID = types.UID(c.RandString()) j.GenerateName = c.RandString() var sec, nsec int64 c.Fuzz(&sec) c.Fuzz(&nsec) j.CreationTimestamp = util.Unix(sec, nsec).Rfc3339Copy() }, func(j *api.ObjectReference, c fuzz.Continue) { // We have to customize the randomization of TypeMetas because their // APIVersion and Kind must remain blank in memory. j.APIVersion = c.RandString() j.Kind = c.RandString() j.Namespace = c.RandString() j.Name = c.RandString() j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) j.FieldPath = c.RandString() }, func(j *api.ListMeta, c fuzz.Continue) { j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) j.SelfLink = c.RandString() }, func(j *api.ListOptions, c fuzz.Continue) { // TODO: add some parsing j.LabelSelector, _ = labels.Parse("a=b") j.FieldSelector, _ = fields.ParseSelector("a=b") }, func(j *api.PodSpec, c fuzz.Continue) { c.FuzzNoCustom(j) // has a default value ttl := int64(30) if c.RandBool() { ttl = int64(c.Uint32()) } j.TerminationGracePeriodSeconds = &ttl }, func(j *api.PodPhase, c fuzz.Continue) { statuses := []api.PodPhase{api.PodPending, api.PodRunning, api.PodFailed, api.PodUnknown} *j = statuses[c.Rand.Intn(len(statuses))] }, func(j *api.PodTemplateSpec, c fuzz.Continue) { // TODO: v1beta1/2 can't round trip a nil template correctly, fix by having v1beta1/2 // conversion compare converted object to nil via DeepEqual j.ObjectMeta = api.ObjectMeta{} c.Fuzz(&j.ObjectMeta) j.ObjectMeta = api.ObjectMeta{Labels: j.ObjectMeta.Labels} j.Spec = api.PodSpec{} c.Fuzz(&j.Spec) }, func(j *api.Binding, c fuzz.Continue) { c.Fuzz(&j.ObjectMeta) j.Target.Name = c.RandString() }, func(j *api.ReplicationControllerSpec, c fuzz.Continue) { c.FuzzNoCustom(j) // fuzz self without calling this function again //j.TemplateRef = nil // this is required for round trip }, func(j *experimental.DeploymentStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) // fuzz self without calling this function again // Ensure that strategyType is one of valid values. strategyTypes := []experimental.DeploymentType{experimental.DeploymentRecreate, experimental.DeploymentRollingUpdate} j.Type = strategyTypes[c.Rand.Intn(len(strategyTypes))] if j.Type != experimental.DeploymentRollingUpdate { j.RollingUpdate = nil } else { rollingUpdate := experimental.RollingUpdateDeployment{} if c.RandBool() { rollingUpdate.MaxUnavailable = util.NewIntOrStringFromInt(int(c.RandUint64())) rollingUpdate.MaxSurge = util.NewIntOrStringFromInt(int(c.RandUint64())) } else { rollingUpdate.MaxSurge = util.NewIntOrStringFromString(fmt.Sprintf("%d%%", c.RandUint64())) } j.RollingUpdate = &rollingUpdate } }, func(j *api.List, c fuzz.Continue) { c.FuzzNoCustom(j) // fuzz self without calling this function again // TODO: uncomment when round trip starts from a versioned object if false { //j.Items == nil { j.Items = []runtime.Object{} } }, func(j *runtime.Object, c fuzz.Continue) { // TODO: uncomment when round trip starts from a versioned object if true { //c.RandBool() { *j = &runtime.Unknown{ TypeMeta: runtime.TypeMeta{Kind: "Something", APIVersion: "unknown"}, RawJSON: []byte(`{"apiVersion":"unknown","kind":"Something","someKey":"someValue"}`), } } else { types := []runtime.Object{&api.Pod{}, &api.ReplicationController{}} t := types[c.Rand.Intn(len(types))] c.Fuzz(t) *j = t } }, func(pb map[docker.Port][]docker.PortBinding, c fuzz.Continue) { // This is necessary because keys with nil values get omitted. // TODO: Is this a bug? pb[docker.Port(c.RandString())] = []docker.PortBinding{ {c.RandString(), c.RandString()}, {c.RandString(), c.RandString()}, } }, func(pm map[string]docker.PortMapping, c fuzz.Continue) { // This is necessary because keys with nil values get omitted. // TODO: Is this a bug? pm[c.RandString()] = docker.PortMapping{ c.RandString(): c.RandString(), } }, func(q *resource.Quantity, c fuzz.Continue) { // Real Quantity fuzz testing is done elsewhere; // this limited subset of functionality survives // round-tripping to v1beta1/2. q.Amount = &inf.Dec{} q.Format = resource.DecimalExponent //q.Amount.SetScale(inf.Scale(-c.Intn(12))) q.Amount.SetUnscaled(c.Int63n(1000)) }, func(q *api.ResourceRequirements, c fuzz.Continue) { randomQuantity := func() resource.Quantity { return *resource.NewQuantity(c.Int63n(1000), resource.DecimalExponent) } q.Limits = make(api.ResourceList) q.Requests = make(api.ResourceList) cpuLimit := randomQuantity() q.Limits[api.ResourceCPU] = *cpuLimit.Copy() q.Requests[api.ResourceCPU] = *cpuLimit.Copy() memoryLimit := randomQuantity() q.Limits[api.ResourceMemory] = *memoryLimit.Copy() q.Requests[api.ResourceMemory] = *memoryLimit.Copy() storageLimit := randomQuantity() q.Limits[api.ResourceStorage] = *storageLimit.Copy() q.Requests[api.ResourceStorage] = *storageLimit.Copy() }, func(q *api.LimitRangeItem, c fuzz.Continue) { randomQuantity := func() resource.Quantity { return *resource.NewQuantity(c.Int63n(1000), resource.DecimalExponent) } cpuLimit := randomQuantity() q.Type = api.LimitTypeContainer q.Default = make(api.ResourceList) q.Default[api.ResourceCPU] = *(cpuLimit.Copy()) q.DefaultRequest = make(api.ResourceList) q.DefaultRequest[api.ResourceCPU] = *(cpuLimit.Copy()) q.Max = make(api.ResourceList) q.Max[api.ResourceCPU] = *(cpuLimit.Copy()) q.Min = make(api.ResourceList) q.Min[api.ResourceCPU] = *(cpuLimit.Copy()) q.MaxLimitRequestRatio = make(api.ResourceList) q.MaxLimitRequestRatio[api.ResourceCPU] = resource.MustParse("10") }, func(p *api.PullPolicy, c fuzz.Continue) { policies := []api.PullPolicy{api.PullAlways, api.PullNever, api.PullIfNotPresent} *p = policies[c.Rand.Intn(len(policies))] }, func(rp *api.RestartPolicy, c fuzz.Continue) { policies := []api.RestartPolicy{api.RestartPolicyAlways, api.RestartPolicyNever, api.RestartPolicyOnFailure} *rp = policies[c.Rand.Intn(len(policies))] }, func(vs *api.VolumeSource, c fuzz.Continue) { // Exactly one of the fields must be set. v := reflect.ValueOf(vs).Elem() i := int(c.RandUint64() % uint64(v.NumField())) v = v.Field(i).Addr() // Use a new fuzzer which cannot populate nil to ensure one field will be set. f := fuzz.New().NilChance(0).NumElements(1, 1) f.Funcs( // Only api.DownwardAPIVolumeFile needs to have a specific func since FieldRef has to be // defaulted to a version otherwise roundtrip will fail // For the remaining volume plugins the default fuzzer is enough. func(m *api.DownwardAPIVolumeFile, c fuzz.Continue) { m.Path = c.RandString() versions := []string{"v1"} m.FieldRef.APIVersion = versions[c.Rand.Intn(len(versions))] m.FieldRef.FieldPath = c.RandString() }, ).Fuzz(v.Interface()) }, func(d *api.DNSPolicy, c fuzz.Continue) { policies := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault} *d = policies[c.Rand.Intn(len(policies))] }, func(p *api.Protocol, c fuzz.Continue) { protocols := []api.Protocol{api.ProtocolTCP, api.ProtocolUDP} *p = protocols[c.Rand.Intn(len(protocols))] }, func(p *api.ServiceAffinity, c fuzz.Continue) { types := []api.ServiceAffinity{api.ServiceAffinityClientIP, api.ServiceAffinityNone} *p = types[c.Rand.Intn(len(types))] }, func(p *api.ServiceType, c fuzz.Continue) { types := []api.ServiceType{api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer} *p = types[c.Rand.Intn(len(types))] }, func(ct *api.Container, c fuzz.Continue) { c.FuzzNoCustom(ct) // fuzz self without calling this function again ct.TerminationMessagePath = "/" + ct.TerminationMessagePath // Must be non-empty }, func(ev *api.EnvVar, c fuzz.Continue) { ev.Name = c.RandString() if c.RandBool() { ev.Value = c.RandString() } else { ev.ValueFrom = &api.EnvVarSource{} ev.ValueFrom.FieldRef = &api.ObjectFieldSelector{} versions := registered.RegisteredVersions ev.ValueFrom.FieldRef.APIVersion = versions[c.Rand.Intn(len(versions))] ev.ValueFrom.FieldRef.FieldPath = c.RandString() } }, func(sc *api.SecurityContext, c fuzz.Continue) { c.FuzzNoCustom(sc) // fuzz self without calling this function again priv := c.RandBool() sc.Privileged = &priv sc.Capabilities = &api.Capabilities{ Add: make([]api.Capability, 0), Drop: make([]api.Capability, 0), } c.Fuzz(&sc.Capabilities.Add) c.Fuzz(&sc.Capabilities.Drop) }, func(e *api.Event, c fuzz.Continue) { c.FuzzNoCustom(e) // fuzz self without calling this function again // Fix event count to 1, otherwise, if a v1beta1 or v1beta2 event has a count set arbitrarily, it's count is ignored if e.FirstTimestamp.IsZero() { e.Count = 1 } else { c.Fuzz(&e.Count) } }, func(s *api.Secret, c fuzz.Continue) { c.FuzzNoCustom(s) // fuzz self without calling this function again s.Type = api.SecretTypeOpaque }, func(pv *api.PersistentVolume, c fuzz.Continue) { c.FuzzNoCustom(pv) // fuzz self without calling this function again types := []api.PersistentVolumePhase{api.VolumeAvailable, api.VolumePending, api.VolumeBound, api.VolumeReleased, api.VolumeFailed} pv.Status.Phase = types[c.Rand.Intn(len(types))] pv.Status.Message = c.RandString() reclamationPolicies := []api.PersistentVolumeReclaimPolicy{api.PersistentVolumeReclaimRecycle, api.PersistentVolumeReclaimRetain} pv.Spec.PersistentVolumeReclaimPolicy = reclamationPolicies[c.Rand.Intn(len(reclamationPolicies))] }, func(pvc *api.PersistentVolumeClaim, c fuzz.Continue) { c.FuzzNoCustom(pvc) // fuzz self without calling this function again types := []api.PersistentVolumeClaimPhase{api.ClaimBound, api.ClaimPending} pvc.Status.Phase = types[c.Rand.Intn(len(types))] }, func(s *api.NamespaceSpec, c fuzz.Continue) { s.Finalizers = []api.FinalizerName{api.FinalizerKubernetes} }, func(s *api.NamespaceStatus, c fuzz.Continue) { s.Phase = api.NamespaceActive }, func(http *api.HTTPGetAction, c fuzz.Continue) { c.FuzzNoCustom(http) // fuzz self without calling this function again http.Path = "/" + http.Path // can't be blank http.Scheme = "x" + http.Scheme // can't be blank }, func(ss *api.ServiceSpec, c fuzz.Continue) { c.FuzzNoCustom(ss) // fuzz self without calling this function again if len(ss.Ports) == 0 { // There must be at least 1 port. ss.Ports = append(ss.Ports, api.ServicePort{}) c.Fuzz(&ss.Ports[0]) } for i := range ss.Ports { switch ss.Ports[i].TargetPort.Kind { case util.IntstrInt: ss.Ports[i].TargetPort.IntVal = 1 + ss.Ports[i].TargetPort.IntVal%65535 // non-zero case util.IntstrString: ss.Ports[i].TargetPort.StrVal = "x" + ss.Ports[i].TargetPort.StrVal // non-empty } } }, func(n *api.Node, c fuzz.Continue) { c.FuzzNoCustom(n) n.Spec.ExternalID = "external" }, func(s *experimental.APIVersion, c fuzz.Continue) { // We can't use c.RandString() here because it may generate empty // string, which will cause tests failure. s.APIGroup = "something" }, ) return f }
func TestRollingUpdater_extractMaxValue(t *testing.T) { tests := []struct { field util.IntOrString original int expected int valid bool }{ { field: util.NewIntOrStringFromInt(1), original: 100, expected: 1, valid: true, }, { field: util.NewIntOrStringFromInt(0), original: 100, expected: 0, valid: true, }, { field: util.NewIntOrStringFromInt(-1), original: 100, valid: false, }, { field: util.NewIntOrStringFromString("10%"), original: 100, expected: 10, valid: true, }, { field: util.NewIntOrStringFromString("100%"), original: 100, expected: 100, valid: true, }, { field: util.NewIntOrStringFromString("200%"), original: 100, expected: 200, valid: true, }, { field: util.NewIntOrStringFromString("0%"), original: 100, expected: 0, valid: true, }, { field: util.NewIntOrStringFromString("-1%"), original: 100, valid: false, }, } for i, test := range tests { t.Logf("evaluating test %d", i) max, err := extractMaxValue(test.field, "field", test.original) if test.valid && err != nil { t.Fatalf("unexpected error: %v", err) } if !test.valid && err == nil { t.Fatalf("expected an error") } if e, a := test.expected, max; e != a { t.Fatalf("expected max %d, got %d", e, a) } } }
func TestValidateDeploymentConfigMissingFields(t *testing.T) { errorCases := map[string]struct { DeploymentConfig api.DeploymentConfig ErrorType fielderrors.ValidationErrorType Field string }{ "missing name": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "", Namespace: "bar"}, Spec: test.OkDeploymentConfigSpec(), }, fielderrors.ValidationErrorTypeRequired, "metadata.name", }, "missing namespace": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: ""}, Spec: test.OkDeploymentConfigSpec(), }, fielderrors.ValidationErrorTypeRequired, "metadata.namespace", }, "invalid name": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "-foo", Namespace: "bar"}, Spec: test.OkDeploymentConfigSpec(), }, fielderrors.ValidationErrorTypeInvalid, "metadata.name", }, "invalid namespace": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "-bar"}, Spec: test.OkDeploymentConfigSpec(), }, fielderrors.ValidationErrorTypeInvalid, "metadata.namespace", }, "missing trigger.type": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Triggers: []api.DeploymentTriggerPolicy{ { ImageChangeParams: &api.DeploymentTriggerImageChangeParams{ ContainerNames: []string{"foo"}, }, }, }, Selector: test.OkSelector(), Strategy: test.OkStrategy(), Template: test.OkPodTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "spec.triggers[0].type", }, "missing Trigger imageChangeParams.from": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Triggers: []api.DeploymentTriggerPolicy{ { Type: api.DeploymentTriggerOnImageChange, ImageChangeParams: &api.DeploymentTriggerImageChangeParams{ ContainerNames: []string{"foo"}, }, }, }, Selector: test.OkSelector(), Strategy: test.OkStrategy(), Template: test.OkPodTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "spec.triggers[0].imageChangeParams.from", }, "invalid Trigger imageChangeParams.from.kind": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Triggers: []api.DeploymentTriggerPolicy{ { Type: api.DeploymentTriggerOnImageChange, ImageChangeParams: &api.DeploymentTriggerImageChangeParams{ From: kapi.ObjectReference{ Kind: "Invalid", Name: "name:tag", }, ContainerNames: []string{"foo"}, }, }, }, Selector: test.OkSelector(), Strategy: test.OkStrategy(), Template: test.OkPodTemplate(), }, }, fielderrors.ValidationErrorTypeInvalid, "spec.triggers[0].imageChangeParams.from.kind", }, "missing Trigger imageChangeParams.containerNames": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Triggers: []api.DeploymentTriggerPolicy{ { Type: api.DeploymentTriggerOnImageChange, ImageChangeParams: &api.DeploymentTriggerImageChangeParams{ From: kapi.ObjectReference{ Kind: "ImageStreamTag", Name: "foo:v1", }, }, }, }, Selector: test.OkSelector(), Strategy: test.OkStrategy(), Template: test.OkPodTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "spec.triggers[0].imageChangeParams.containerNames", }, "missing strategy.type": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Triggers: manualTrigger(), Selector: test.OkSelector(), Strategy: api.DeploymentStrategy{ CustomParams: test.OkCustomParams(), }, Template: test.OkPodTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "spec.strategy.type", }, "missing strategy.customParams": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Triggers: manualTrigger(), Selector: test.OkSelector(), Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeCustom, }, Template: test.OkPodTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "spec.strategy.customParams", }, "missing spec.strategy.customParams.image": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Triggers: manualTrigger(), Selector: test.OkSelector(), Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeCustom, CustomParams: &api.CustomDeploymentStrategyParams{}, }, Template: test.OkPodTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "spec.strategy.customParams.image", }, "missing spec.strategy.recreateParams.pre.failurePolicy": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeRecreate, RecreateParams: &api.RecreateDeploymentStrategyParams{ Pre: &api.LifecycleHook{ ExecNewPod: &api.ExecNewPodHook{ Command: []string{"cmd"}, ContainerName: "container", }, }, }, }, Template: test.OkPodTemplate(), Selector: test.OkSelector(), }, }, fielderrors.ValidationErrorTypeRequired, "spec.strategy.recreateParams.pre.failurePolicy", }, "missing spec.strategy.recreateParams.pre.execNewPod": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeRecreate, RecreateParams: &api.RecreateDeploymentStrategyParams{ Pre: &api.LifecycleHook{ FailurePolicy: api.LifecycleHookFailurePolicyRetry, }, }, }, Template: test.OkPodTemplate(), Selector: test.OkSelector(), }, }, fielderrors.ValidationErrorTypeRequired, "spec.strategy.recreateParams.pre.execNewPod", }, "missing spec.strategy.recreateParams.pre.execNewPod.command": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeRecreate, RecreateParams: &api.RecreateDeploymentStrategyParams{ Pre: &api.LifecycleHook{ FailurePolicy: api.LifecycleHookFailurePolicyRetry, ExecNewPod: &api.ExecNewPodHook{ ContainerName: "container", }, }, }, }, Template: test.OkPodTemplate(), Selector: test.OkSelector(), }, }, fielderrors.ValidationErrorTypeRequired, "spec.strategy.recreateParams.pre.execNewPod.command", }, "missing spec.strategy.recreateParams.pre.execNewPod.containerName": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeRecreate, RecreateParams: &api.RecreateDeploymentStrategyParams{ Pre: &api.LifecycleHook{ FailurePolicy: api.LifecycleHookFailurePolicyRetry, ExecNewPod: &api.ExecNewPodHook{ Command: []string{"cmd"}, }, }, }, }, Template: test.OkPodTemplate(), Selector: test.OkSelector(), }, }, fielderrors.ValidationErrorTypeRequired, "spec.strategy.recreateParams.pre.execNewPod.containerName", }, "invalid spec.strategy.recreateParams.pre.execNewPod.volumes": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeRecreate, RecreateParams: &api.RecreateDeploymentStrategyParams{ Pre: &api.LifecycleHook{ FailurePolicy: api.LifecycleHookFailurePolicyRetry, ExecNewPod: &api.ExecNewPodHook{ ContainerName: "container", Command: []string{"cmd"}, Volumes: []string{"good", ""}, }, }, }, }, Template: test.OkPodTemplate(), Selector: test.OkSelector(), }, }, fielderrors.ValidationErrorTypeInvalid, "spec.strategy.recreateParams.pre.execNewPod.volumes[1]", }, "invalid spec.strategy.rollingParams.intervalSeconds": { rollingConfig(-20, 1, 1), fielderrors.ValidationErrorTypeInvalid, "spec.strategy.rollingParams.intervalSeconds", }, "invalid spec.strategy.rollingParams.updatePeriodSeconds": { rollingConfig(1, -20, 1), fielderrors.ValidationErrorTypeInvalid, "spec.strategy.rollingParams.updatePeriodSeconds", }, "invalid spec.strategy.rollingParams.timeoutSeconds": { rollingConfig(1, 1, -20), fielderrors.ValidationErrorTypeInvalid, "spec.strategy.rollingParams.timeoutSeconds", }, "missing spec.strategy.rollingParams.pre.failurePolicy": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeRolling, RollingParams: &api.RollingDeploymentStrategyParams{ IntervalSeconds: mkint64p(1), UpdatePeriodSeconds: mkint64p(1), TimeoutSeconds: mkint64p(20), MaxSurge: kutil.NewIntOrStringFromInt(1), Pre: &api.LifecycleHook{ ExecNewPod: &api.ExecNewPodHook{ Command: []string{"cmd"}, ContainerName: "container", }, }, }, }, Template: test.OkPodTemplate(), Selector: test.OkSelector(), }, }, fielderrors.ValidationErrorTypeRequired, "spec.strategy.rollingParams.pre.failurePolicy", }, "both maxSurge and maxUnavailable 0 spec.strategy.rollingParams.maxUnavailable": { rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromInt(0)), fielderrors.ValidationErrorTypeInvalid, "spec.strategy.rollingParams.maxUnavailable", }, "invalid lower bound spec.strategy.rollingParams.maxUnavailable": { rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromInt(-100)), fielderrors.ValidationErrorTypeInvalid, "spec.strategy.rollingParams.maxUnavailable", }, "invalid lower bound spec.strategy.rollingParams.maxSurge": { rollingConfigMax(kutil.NewIntOrStringFromInt(-1), kutil.NewIntOrStringFromInt(0)), fielderrors.ValidationErrorTypeInvalid, "spec.strategy.rollingParams.maxSurge", }, "both maxSurge and maxUnavailable 0 percent spec.strategy.rollingParams.maxUnavailable": { rollingConfigMax(kutil.NewIntOrStringFromString("0%"), kutil.NewIntOrStringFromString("0%")), fielderrors.ValidationErrorTypeInvalid, "spec.strategy.rollingParams.maxUnavailable", }, "invalid lower bound percent spec.strategy.rollingParams.maxUnavailable": { rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromString("-1%")), fielderrors.ValidationErrorTypeInvalid, "spec.strategy.rollingParams.maxUnavailable", }, "invalid upper bound percent spec.strategy.rollingParams.maxUnavailable": { rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromString("101%")), fielderrors.ValidationErrorTypeInvalid, "spec.strategy.rollingParams.maxUnavailable", }, "invalid percent spec.strategy.rollingParams.maxUnavailable": { rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromString("foo")), fielderrors.ValidationErrorTypeInvalid, "spec.strategy.rollingParams.maxUnavailable", }, "invalid percent spec.strategy.rollingParams.maxSurge": { rollingConfigMax(kutil.NewIntOrStringFromString("foo"), kutil.NewIntOrStringFromString("100%")), fielderrors.ValidationErrorTypeInvalid, "spec.strategy.rollingParams.maxSurge", }, } for testName, v := range errorCases { errs := ValidateDeploymentConfig(&v.DeploymentConfig) if len(v.ErrorType) == 0 { if len(errs) > 0 { for _, e := range errs { t.Errorf("%s: unexpected error: %s", testName, e) } } continue } if len(errs) == 0 { t.Errorf("%s: expected test failure, got success", testName) } for i := range errs { if got, exp := errs[i].(*fielderrors.ValidationError).Type, v.ErrorType; got != exp { t.Errorf("%s: expected error \"%v\" to have type %q, but got %q", testName, errs[i], exp, got) } if got, exp := errs[i].(*fielderrors.ValidationError).Field, v.Field; got != exp { t.Errorf("%s: expected error \"%v\" to have field %q, but got %q", testName, errs[i], exp, got) } } } }
func Test_convert_v1_RollingDeploymentStrategyParams_To_api_RollingDeploymentStrategyParams(t *testing.T) { tests := []struct { in *RollingDeploymentStrategyParams out *newer.RollingDeploymentStrategyParams }{ { in: &RollingDeploymentStrategyParams{ UpdatePeriodSeconds: newInt64(5), IntervalSeconds: newInt64(6), TimeoutSeconds: newInt64(7), UpdatePercent: newInt(-25), }, out: &newer.RollingDeploymentStrategyParams{ UpdatePeriodSeconds: newInt64(5), IntervalSeconds: newInt64(6), TimeoutSeconds: newInt64(7), UpdatePercent: newInt(-25), MaxSurge: util.NewIntOrStringFromInt(0), MaxUnavailable: util.NewIntOrStringFromString("25%"), }, }, { in: &RollingDeploymentStrategyParams{ UpdatePeriodSeconds: newInt64(5), IntervalSeconds: newInt64(6), TimeoutSeconds: newInt64(7), UpdatePercent: newInt(25), }, out: &newer.RollingDeploymentStrategyParams{ UpdatePeriodSeconds: newInt64(5), IntervalSeconds: newInt64(6), TimeoutSeconds: newInt64(7), UpdatePercent: newInt(25), MaxSurge: util.NewIntOrStringFromString("25%"), MaxUnavailable: util.NewIntOrStringFromInt(0), }, }, { in: &RollingDeploymentStrategyParams{ UpdatePeriodSeconds: newInt64(5), IntervalSeconds: newInt64(6), TimeoutSeconds: newInt64(7), MaxSurge: newIntOrString(util.NewIntOrStringFromInt(10)), MaxUnavailable: newIntOrString(util.NewIntOrStringFromInt(20)), }, out: &newer.RollingDeploymentStrategyParams{ UpdatePeriodSeconds: newInt64(5), IntervalSeconds: newInt64(6), TimeoutSeconds: newInt64(7), MaxSurge: util.NewIntOrStringFromInt(10), MaxUnavailable: util.NewIntOrStringFromInt(20), }, }, } for _, test := range tests { out := &newer.RollingDeploymentStrategyParams{} if err := kapi.Scheme.Convert(test.in, out); err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(out, test.out) { t.Errorf("got different than expected:\nA:\t%#v\nB:\t%#v\n\nDiff:\n%s\n\n%s", out, test.out, util.ObjectDiff(test.out, out), util.ObjectGoPrintSideBySide(test.out, out)) } } }
func addDefaultingFuncs() { api.Scheme.AddDefaultingFuncs( func(obj *APIVersion) { if len(obj.APIGroup) == 0 { obj.APIGroup = "experimental" } }, func(obj *ReplicationController) { var labels map[string]string if obj.Spec.Template != nil { labels = obj.Spec.Template.Labels } // TODO: support templates defined elsewhere when we support them in the API if labels != nil { if len(obj.Spec.Selector) == 0 { obj.Spec.Selector = labels } if len(obj.Labels) == 0 { obj.Labels = labels } } if obj.Spec.Replicas == nil { obj.Spec.Replicas = new(int) *obj.Spec.Replicas = 1 } }, func(obj *Volume) { if util.AllPtrFieldsNil(&obj.VolumeSource) { obj.VolumeSource = VolumeSource{ EmptyDir: &EmptyDirVolumeSource{}, } } }, func(obj *ContainerPort) { if obj.Protocol == "" { obj.Protocol = ProtocolTCP } // Carry conversion to make port case valid switch strings.ToUpper(string(obj.Protocol)) { case string(ProtocolTCP): obj.Protocol = ProtocolTCP case string(ProtocolUDP): obj.Protocol = ProtocolUDP } }, func(obj *Container) { if obj.ImagePullPolicy == "" { // TODO(dchen1107): Move ParseImageName code to pkg/util parts := strings.Split(obj.Image, ":") // Check image tag if parts[len(parts)-1] == "latest" { obj.ImagePullPolicy = PullAlways } else { obj.ImagePullPolicy = PullIfNotPresent } } if obj.TerminationMessagePath == "" { obj.TerminationMessagePath = TerminationMessagePathDefault } }, func(obj *ServiceSpec) { if obj.SessionAffinity == "" { obj.SessionAffinity = ServiceAffinityNone } if obj.Type == "" { obj.Type = ServiceTypeClusterIP } for i := range obj.Ports { sp := &obj.Ports[i] if sp.Protocol == "" { sp.Protocol = ProtocolTCP } if sp.TargetPort == util.NewIntOrStringFromInt(0) || sp.TargetPort == util.NewIntOrStringFromString("") { sp.TargetPort = util.NewIntOrStringFromInt(sp.Port) } } // Carry conversion if len(obj.ClusterIP) == 0 && len(obj.DeprecatedPortalIP) > 0 { obj.ClusterIP = obj.DeprecatedPortalIP } }, func(obj *ServicePort) { // Carry conversion to make port case valid switch strings.ToUpper(string(obj.Protocol)) { case string(ProtocolTCP): obj.Protocol = ProtocolTCP case string(ProtocolUDP): obj.Protocol = ProtocolUDP } }, func(obj *PodSpec) { if obj.DNSPolicy == "" { obj.DNSPolicy = DNSClusterFirst } if obj.RestartPolicy == "" { obj.RestartPolicy = RestartPolicyAlways } if obj.HostNetwork { defaultHostNetworkPorts(&obj.Containers) } // Carry migration from serviceAccount to serviceAccountName if len(obj.ServiceAccountName) == 0 && len(obj.DeprecatedServiceAccount) > 0 { obj.ServiceAccountName = obj.DeprecatedServiceAccount } // Carry migration from host to nodeName if len(obj.NodeName) == 0 && len(obj.DeprecatedHost) > 0 { obj.NodeName = obj.DeprecatedHost } if obj.TerminationGracePeriodSeconds == nil { period := int64(DefaultTerminationGracePeriodSeconds) obj.TerminationGracePeriodSeconds = &period } }, func(obj *Probe) { if obj.TimeoutSeconds == 0 { obj.TimeoutSeconds = 1 } }, func(obj *Secret) { if obj.Type == "" { obj.Type = SecretTypeOpaque } }, func(obj *PersistentVolume) { if obj.Status.Phase == "" { obj.Status.Phase = VolumePending } if obj.Spec.PersistentVolumeReclaimPolicy == "" { obj.Spec.PersistentVolumeReclaimPolicy = PersistentVolumeReclaimRetain } }, func(obj *PersistentVolumeClaim) { if obj.Status.Phase == "" { obj.Status.Phase = ClaimPending } }, func(obj *Endpoints) { for i := range obj.Subsets { ss := &obj.Subsets[i] for i := range ss.Ports { ep := &ss.Ports[i] if ep.Protocol == "" { ep.Protocol = ProtocolTCP } } } }, func(obj *EndpointPort) { // Carry conversion to make port case valid switch strings.ToUpper(string(obj.Protocol)) { case string(ProtocolTCP): obj.Protocol = ProtocolTCP case string(ProtocolUDP): obj.Protocol = ProtocolUDP } }, func(obj *HTTPGetAction) { if obj.Path == "" { obj.Path = "/" } if obj.Scheme == "" { obj.Scheme = URISchemeHTTP } }, func(obj *NamespaceStatus) { if obj.Phase == "" { obj.Phase = NamespaceActive } }, func(obj *Node) { if obj.Spec.ExternalID == "" { obj.Spec.ExternalID = obj.Name } }, func(obj *ObjectFieldSelector) { if obj.APIVersion == "" { obj.APIVersion = "v1" } }, func(obj *ResourceRequirements) { // Set requests to limits if requests are not specified (but limits are). if obj.Limits != nil { if obj.Requests == nil { obj.Requests = make(ResourceList) } for key, value := range obj.Limits { if _, exists := obj.Requests[key]; !exists { obj.Requests[key] = *(value.Copy()) } } } }, func(obj *LimitRangeItem) { // for container limits, we apply default values if obj.Type == LimitTypeContainer { if obj.Default == nil { obj.Default = make(ResourceList) } if obj.DefaultRequest == nil { obj.DefaultRequest = make(ResourceList) } // If a default limit is unspecified, but the max is specified, default the limit to the max for key, value := range obj.Max { if _, exists := obj.Default[key]; !exists { obj.Default[key] = *(value.Copy()) } } // If a default limit is specified, but the default request is not, default request to limit for key, value := range obj.Default { if _, exists := obj.DefaultRequest[key]; !exists { obj.DefaultRequest[key] = *(value.Copy()) } } // If a default request is not specified, but the min is provided, default request to the min for key, value := range obj.Min { if _, exists := obj.DefaultRequest[key]; !exists { obj.DefaultRequest[key] = *(value.Copy()) } } } }, ) }
func TestValidateDeploymentConfigMissingFields(t *testing.T) { errorCases := map[string]struct { D api.DeploymentConfig T fielderrors.ValidationErrorType F string }{ "missing name": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "", Namespace: "bar"}, Template: test.OkDeploymentTemplate(), }, fielderrors.ValidationErrorTypeRequired, "metadata.name", }, "missing namespace": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: ""}, Template: test.OkDeploymentTemplate(), }, fielderrors.ValidationErrorTypeRequired, "metadata.namespace", }, "invalid name": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "-foo", Namespace: "bar"}, Template: test.OkDeploymentTemplate(), }, fielderrors.ValidationErrorTypeInvalid, "metadata.name", }, "invalid namespace": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "-bar"}, Template: test.OkDeploymentTemplate(), }, fielderrors.ValidationErrorTypeInvalid, "metadata.namespace", }, "missing trigger.type": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Triggers: []api.DeploymentTriggerPolicy{ { ImageChangeParams: &api.DeploymentTriggerImageChangeParams{ ContainerNames: []string{"foo"}, }, }, }, Template: test.OkDeploymentTemplate(), }, fielderrors.ValidationErrorTypeRequired, "triggers[0].type", }, "missing Trigger imageChangeParams.from": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Triggers: []api.DeploymentTriggerPolicy{ { Type: api.DeploymentTriggerOnImageChange, ImageChangeParams: &api.DeploymentTriggerImageChangeParams{ ContainerNames: []string{"foo"}, }, }, }, Template: test.OkDeploymentTemplate(), }, fielderrors.ValidationErrorTypeRequired, "triggers[0].imageChangeParams.from", }, "invalid Trigger imageChangeParams.from.kind": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Triggers: []api.DeploymentTriggerPolicy{ { Type: api.DeploymentTriggerOnImageChange, ImageChangeParams: &api.DeploymentTriggerImageChangeParams{ From: kapi.ObjectReference{ Kind: "Invalid", Name: "name", }, ContainerNames: []string{"foo"}, }, }, }, Template: test.OkDeploymentTemplate(), }, fielderrors.ValidationErrorTypeInvalid, "triggers[0].imageChangeParams.from.kind", }, "both fields illegal Trigger imageChangeParams.repositoryName": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Triggers: []api.DeploymentTriggerPolicy{ { Type: api.DeploymentTriggerOnImageChange, ImageChangeParams: &api.DeploymentTriggerImageChangeParams{ ContainerNames: []string{"foo"}, RepositoryName: "name", From: kapi.ObjectReference{ Name: "other", }, }, }, }, Template: test.OkDeploymentTemplate(), }, fielderrors.ValidationErrorTypeInvalid, "triggers[0].imageChangeParams.repositoryName", }, "missing Trigger imageChangeParams.containerNames": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Triggers: []api.DeploymentTriggerPolicy{ { Type: api.DeploymentTriggerOnImageChange, ImageChangeParams: &api.DeploymentTriggerImageChangeParams{ RepositoryName: "foo", }, }, }, Template: test.OkDeploymentTemplate(), }, fielderrors.ValidationErrorTypeRequired, "triggers[0].imageChangeParams.containerNames", }, "missing strategy.type": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Triggers: manualTrigger(), Template: api.DeploymentTemplate{ Strategy: api.DeploymentStrategy{ CustomParams: test.OkCustomParams(), }, ControllerTemplate: test.OkControllerTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "template.strategy.type", }, "missing strategy.customParams": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Triggers: manualTrigger(), Template: api.DeploymentTemplate{ Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeCustom, }, ControllerTemplate: test.OkControllerTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "template.strategy.customParams", }, "missing template.strategy.customParams.image": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Triggers: manualTrigger(), Template: api.DeploymentTemplate{ Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeCustom, CustomParams: &api.CustomDeploymentStrategyParams{}, }, ControllerTemplate: test.OkControllerTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "template.strategy.customParams.image", }, "missing template.strategy.recreateParams.pre.failurePolicy": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Template: api.DeploymentTemplate{ Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeRecreate, RecreateParams: &api.RecreateDeploymentStrategyParams{ Pre: &api.LifecycleHook{ ExecNewPod: &api.ExecNewPodHook{ Command: []string{"cmd"}, ContainerName: "container", }, }, }, }, ControllerTemplate: test.OkControllerTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "template.strategy.recreateParams.pre.failurePolicy", }, "missing template.strategy.recreateParams.pre.execNewPod": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Template: api.DeploymentTemplate{ Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeRecreate, RecreateParams: &api.RecreateDeploymentStrategyParams{ Pre: &api.LifecycleHook{ FailurePolicy: api.LifecycleHookFailurePolicyRetry, }, }, }, ControllerTemplate: test.OkControllerTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "template.strategy.recreateParams.pre.execNewPod", }, "missing template.strategy.recreateParams.pre.execNewPod.command": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Template: api.DeploymentTemplate{ Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeRecreate, RecreateParams: &api.RecreateDeploymentStrategyParams{ Pre: &api.LifecycleHook{ FailurePolicy: api.LifecycleHookFailurePolicyRetry, ExecNewPod: &api.ExecNewPodHook{ ContainerName: "container", }, }, }, }, ControllerTemplate: test.OkControllerTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "template.strategy.recreateParams.pre.execNewPod.command", }, "missing template.strategy.recreateParams.pre.execNewPod.containerName": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Template: api.DeploymentTemplate{ Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeRecreate, RecreateParams: &api.RecreateDeploymentStrategyParams{ Pre: &api.LifecycleHook{ FailurePolicy: api.LifecycleHookFailurePolicyRetry, ExecNewPod: &api.ExecNewPodHook{ Command: []string{"cmd"}, }, }, }, }, ControllerTemplate: test.OkControllerTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "template.strategy.recreateParams.pre.execNewPod.containerName", }, "invalid template.strategy.rollingParams.intervalSeconds": { rollingConfig(-20, 1, 1), fielderrors.ValidationErrorTypeInvalid, "template.strategy.rollingParams.intervalSeconds", }, "invalid template.strategy.rollingParams.updatePeriodSeconds": { rollingConfig(1, -20, 1), fielderrors.ValidationErrorTypeInvalid, "template.strategy.rollingParams.updatePeriodSeconds", }, "invalid template.strategy.rollingParams.timeoutSeconds": { rollingConfig(1, 1, -20), fielderrors.ValidationErrorTypeInvalid, "template.strategy.rollingParams.timeoutSeconds", }, "missing template.strategy.rollingParams.pre.failurePolicy": { api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Template: api.DeploymentTemplate{ Strategy: api.DeploymentStrategy{ Type: api.DeploymentStrategyTypeRolling, RollingParams: &api.RollingDeploymentStrategyParams{ IntervalSeconds: mkint64p(1), UpdatePeriodSeconds: mkint64p(1), TimeoutSeconds: mkint64p(20), MaxSurge: kutil.NewIntOrStringFromInt(1), Pre: &api.LifecycleHook{ ExecNewPod: &api.ExecNewPodHook{ Command: []string{"cmd"}, ContainerName: "container", }, }, }, }, ControllerTemplate: test.OkControllerTemplate(), }, }, fielderrors.ValidationErrorTypeRequired, "template.strategy.rollingParams.pre.failurePolicy", }, "both maxSurge and maxUnavailable 0 template.strategy.rollingParams.maxUnavailable": { rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromInt(0)), fielderrors.ValidationErrorTypeInvalid, "template.strategy.rollingParams.maxUnavailable", }, "invalid lower bound template.strategy.rollingParams.maxUnavailable": { rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromInt(-100)), fielderrors.ValidationErrorTypeInvalid, "template.strategy.rollingParams.maxUnavailable", }, "invalid lower bound template.strategy.rollingParams.maxSurge": { rollingConfigMax(kutil.NewIntOrStringFromInt(-1), kutil.NewIntOrStringFromInt(0)), fielderrors.ValidationErrorTypeInvalid, "template.strategy.rollingParams.maxSurge", }, "both maxSurge and maxUnavailable 0 percent template.strategy.rollingParams.maxUnavailable": { rollingConfigMax(kutil.NewIntOrStringFromString("0%"), kutil.NewIntOrStringFromString("0%")), fielderrors.ValidationErrorTypeInvalid, "template.strategy.rollingParams.maxUnavailable", }, "invalid lower bound percent template.strategy.rollingParams.maxUnavailable": { rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromString("-1%")), fielderrors.ValidationErrorTypeInvalid, "template.strategy.rollingParams.maxUnavailable", }, "invalid upper bound percent template.strategy.rollingParams.maxUnavailable": { rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromString("101%")), fielderrors.ValidationErrorTypeInvalid, "template.strategy.rollingParams.maxUnavailable", }, "invalid percent template.strategy.rollingParams.maxUnavailable": { rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromString("foo")), fielderrors.ValidationErrorTypeInvalid, "template.strategy.rollingParams.maxUnavailable", }, "invalid percent template.strategy.rollingParams.maxSurge": { rollingConfigMax(kutil.NewIntOrStringFromString("foo"), kutil.NewIntOrStringFromString("100%")), fielderrors.ValidationErrorTypeInvalid, "template.strategy.rollingParams.maxSurge", }, } for k, v := range errorCases { errs := ValidateDeploymentConfig(&v.D) if len(v.T) == 0 { if len(errs) > 0 { for _, e := range errs { t.Errorf("unexpected error for %s: %s", k, e) } } continue } if len(errs) == 0 { t.Errorf("Expected failure for scenario %s", k) } for i := range errs { if errs[i].(*fielderrors.ValidationError).Type != v.T { t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i]) } if errs[i].(*fielderrors.ValidationError).Field != v.F { t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i]) } } } }
func fuzzInternalObject(t *testing.T, forVersion string, item runtime.Object, seed int64) runtime.Object { f := apitesting.FuzzerFor(t, forVersion, rand.NewSource(seed)) f.Funcs( // Roles and RoleBindings maps are never nil func(j *authorizationapi.Policy, c fuzz.Continue) { j.Roles = make(map[string]*authorizationapi.Role) }, func(j *authorizationapi.PolicyBinding, c fuzz.Continue) { j.RoleBindings = make(map[string]*authorizationapi.RoleBinding) }, func(j *authorizationapi.ClusterPolicy, c fuzz.Continue) { j.Roles = make(map[string]*authorizationapi.ClusterRole) }, func(j *authorizationapi.ClusterPolicyBinding, c fuzz.Continue) { j.RoleBindings = make(map[string]*authorizationapi.ClusterRoleBinding) }, func(j *authorizationapi.RoleBinding, c fuzz.Continue) { c.FuzzNoCustom(j) for i := range j.Subjects { kinds := []string{authorizationapi.UserKind, authorizationapi.SystemUserKind, authorizationapi.GroupKind, authorizationapi.SystemGroupKind, authorizationapi.ServiceAccountKind} j.Subjects[i].Kind = kinds[c.Intn(len(kinds))] switch j.Subjects[i].Kind { case authorizationapi.UserKind: j.Subjects[i].Namespace = "" if valid, _ := uservalidation.ValidateUserName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("validusername%d", i) } case authorizationapi.GroupKind: j.Subjects[i].Namespace = "" if valid, _ := uservalidation.ValidateGroupName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("validgroupname%d", i) } case authorizationapi.ServiceAccountKind: if valid, _ := validation.ValidateNamespaceName(j.Subjects[i].Namespace, false); !valid { j.Subjects[i].Namespace = fmt.Sprintf("sanamespacehere%d", i) } if valid, _ := validation.ValidateServiceAccountName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("sanamehere%d", i) } case authorizationapi.SystemUserKind, authorizationapi.SystemGroupKind: j.Subjects[i].Namespace = "" j.Subjects[i].Name = ":" + j.Subjects[i].Name } j.Subjects[i].UID = types.UID("") j.Subjects[i].APIVersion = "" j.Subjects[i].ResourceVersion = "" j.Subjects[i].FieldPath = "" } }, func(j *authorizationapi.ClusterRoleBinding, c fuzz.Continue) { c.FuzzNoCustom(j) for i := range j.Subjects { kinds := []string{authorizationapi.UserKind, authorizationapi.SystemUserKind, authorizationapi.GroupKind, authorizationapi.SystemGroupKind, authorizationapi.ServiceAccountKind} j.Subjects[i].Kind = kinds[c.Intn(len(kinds))] switch j.Subjects[i].Kind { case authorizationapi.UserKind: j.Subjects[i].Namespace = "" if valid, _ := uservalidation.ValidateUserName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("validusername%d", i) } case authorizationapi.GroupKind: j.Subjects[i].Namespace = "" if valid, _ := uservalidation.ValidateGroupName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("validgroupname%d", i) } case authorizationapi.ServiceAccountKind: if valid, _ := validation.ValidateNamespaceName(j.Subjects[i].Namespace, false); !valid { j.Subjects[i].Namespace = fmt.Sprintf("sanamespacehere%d", i) } if valid, _ := validation.ValidateServiceAccountName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("sanamehere%d", i) } case authorizationapi.SystemUserKind, authorizationapi.SystemGroupKind: j.Subjects[i].Namespace = "" j.Subjects[i].Name = ":" + j.Subjects[i].Name } j.Subjects[i].UID = types.UID("") j.Subjects[i].APIVersion = "" j.Subjects[i].ResourceVersion = "" j.Subjects[i].FieldPath = "" } }, func(j *template.Template, c fuzz.Continue) { c.Fuzz(&j.ObjectMeta) c.Fuzz(&j.Parameters) // TODO: replace with structured type definition j.Objects = []runtime.Object{} }, func(j *image.Image, c fuzz.Continue) { c.Fuzz(&j.ObjectMeta) c.Fuzz(&j.DockerImageMetadata) j.DockerImageMetadata.APIVersion = "" j.DockerImageMetadata.Kind = "" j.DockerImageMetadataVersion = []string{"pre012", "1.0"}[c.Rand.Intn(2)] j.DockerImageReference = c.RandString() }, func(j *image.ImageStreamMapping, c fuzz.Continue) { c.FuzzNoCustom(j) j.DockerImageRepository = "" }, func(j *image.ImageImportSpec, c fuzz.Continue) { c.FuzzNoCustom(j) if j.To == nil { // To is defaulted to be not nil j.To = &api.LocalObjectReference{} } }, func(j *image.ImageStreamImage, c fuzz.Continue) { c.Fuzz(&j.Image) // because we de-embedded Image from ImageStreamImage, in order to round trip // successfully, the ImageStreamImage's ObjectMeta must match the Image's. j.ObjectMeta = j.Image.ObjectMeta }, func(j *image.ImageStreamSpec, c fuzz.Continue) { c.FuzzNoCustom(j) // if the generated fuzz value has a tag or image id, strip it if strings.ContainsAny(j.DockerImageRepository, ":@") { j.DockerImageRepository = "" } if j.Tags == nil { j.Tags = make(map[string]image.TagReference) } }, func(j *image.ImageStreamStatus, c fuzz.Continue) { c.FuzzNoCustom(j) // if the generated fuzz value has a tag or image id, strip it if strings.ContainsAny(j.DockerImageRepository, ":@") { j.DockerImageRepository = "" } }, func(j *image.ImageStreamTag, c fuzz.Continue) { c.Fuzz(&j.Image) // because we de-embedded Image from ImageStreamTag, in order to round trip // successfully, the ImageStreamTag's ObjectMeta must match the Image's. j.ObjectMeta = j.Image.ObjectMeta }, func(j *image.TagReference, c fuzz.Continue) { c.FuzzNoCustom(j) if j.From != nil { specs := []string{"", "ImageStreamTag", "ImageStreamImage"} j.From.Kind = specs[c.Intn(len(specs))] } }, func(j *build.SourceBuildStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) j.From.Kind = "ImageStreamTag" j.From.Name = "image:tag" j.From.APIVersion = "" j.From.ResourceVersion = "" j.From.FieldPath = "" }, func(j *build.CustomBuildStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) j.From.Kind = "ImageStreamTag" j.From.Name = "image:tag" j.From.APIVersion = "" j.From.ResourceVersion = "" j.From.FieldPath = "" }, func(j *build.DockerBuildStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) j.From.Kind = "ImageStreamTag" j.From.Name = "image:tag" j.From.APIVersion = "" j.From.ResourceVersion = "" j.From.FieldPath = "" }, func(j *build.BuildOutput, c fuzz.Continue) { c.FuzzNoCustom(j) if j.To != nil && (len(j.To.Kind) == 0 || j.To.Kind == "ImageStream") { j.To.Kind = "ImageStreamTag" } if j.To != nil && strings.Contains(j.To.Name, ":") { j.To.Name = strings.Replace(j.To.Name, ":", "-", -1) } }, func(j *route.RouteSpec, c fuzz.Continue) { c.FuzzNoCustom(j) j.To = api.ObjectReference{ Kind: "Service", Name: j.To.Name, } }, func(j *route.TLSConfig, c fuzz.Continue) { c.FuzzNoCustom(j) if len(j.Termination) == 0 && len(j.DestinationCACertificate) == 0 { j.Termination = route.TLSTerminationEdge } }, func(j *deploy.DeploymentConfig, c fuzz.Continue) { c.FuzzNoCustom(j) j.Spec.Triggers = []deploy.DeploymentTriggerPolicy{{Type: deploy.DeploymentTriggerOnConfigChange}} if forVersion == "v1beta3" { // v1beta3 does not contain the PodSecurityContext type. For this API version, only fuzz // the host namespace fields. The fields set to nil here are the other fields of the // PodSecurityContext that will not roundtrip correctly from internal->v1beta3->internal. j.Spec.Template.Spec.SecurityContext.SELinuxOptions = nil j.Spec.Template.Spec.SecurityContext.RunAsUser = nil j.Spec.Template.Spec.SecurityContext.RunAsNonRoot = nil j.Spec.Template.Spec.SecurityContext.SupplementalGroups = nil j.Spec.Template.Spec.SecurityContext.FSGroup = nil } }, func(j *deploy.DeploymentStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) strategyTypes := []deploy.DeploymentStrategyType{deploy.DeploymentStrategyTypeRecreate, deploy.DeploymentStrategyTypeRolling, deploy.DeploymentStrategyTypeCustom} j.Type = strategyTypes[c.Rand.Intn(len(strategyTypes))] switch j.Type { case deploy.DeploymentStrategyTypeRolling: params := &deploy.RollingDeploymentStrategyParams{} randInt64 := func() *int64 { p := int64(c.RandUint64()) return &p } params.TimeoutSeconds = randInt64() params.IntervalSeconds = randInt64() params.UpdatePeriodSeconds = randInt64() policyTypes := []deploy.LifecycleHookFailurePolicy{ deploy.LifecycleHookFailurePolicyRetry, deploy.LifecycleHookFailurePolicyAbort, deploy.LifecycleHookFailurePolicyIgnore, } if c.RandBool() { params.Pre = &deploy.LifecycleHook{ FailurePolicy: policyTypes[c.Rand.Intn(len(policyTypes))], ExecNewPod: &deploy.ExecNewPodHook{ ContainerName: c.RandString(), }, } } if c.RandBool() { params.Post = &deploy.LifecycleHook{ FailurePolicy: policyTypes[c.Rand.Intn(len(policyTypes))], ExecNewPod: &deploy.ExecNewPodHook{ ContainerName: c.RandString(), }, } } if c.RandBool() { params.MaxUnavailable = util.NewIntOrStringFromInt(int(c.RandUint64())) params.MaxSurge = util.NewIntOrStringFromInt(int(c.RandUint64())) } else { params.MaxSurge = util.NewIntOrStringFromString(fmt.Sprintf("%d%%", c.RandUint64())) params.MaxUnavailable = util.NewIntOrStringFromString(fmt.Sprintf("%d%%", c.RandUint64())) } j.RollingParams = params default: j.RollingParams = nil } }, func(j *deploy.DeploymentCauseImageTrigger, c fuzz.Continue) { c.FuzzNoCustom(j) specs := []string{"", "a/b", "a/b/c", "a:5000/b/c", "a/b", "a/b"} tags := []string{"stuff", "other"} j.From.Name = specs[c.Intn(len(specs))] if len(j.From.Name) > 0 { j.From.Name = image.JoinImageStreamTag(j.From.Name, tags[c.Intn(len(tags))]) } }, func(j *deploy.DeploymentTriggerImageChangeParams, c fuzz.Continue) { c.FuzzNoCustom(j) specs := []string{"a/b", "a/b/c", "a:5000/b/c", "a/b:latest", "a/b@test"} j.From.Kind = "DockerImage" j.From.Name = specs[c.Intn(len(specs))] }, func(j *runtime.EmbeddedObject, c fuzz.Continue) { // runtime.EmbeddedObject causes a panic inside of fuzz because runtime.Object isn't handled. }, func(t *time.Time, c fuzz.Continue) { // This is necessary because the standard fuzzed time.Time object is // completely nil, but when JSON unmarshals dates it fills in the // unexported loc field with the time.UTC object, resulting in // reflect.DeepEqual returning false in the round trip tests. We solve it // by using a date that will be identical to the one JSON unmarshals. *t = time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC) }, func(u64 *uint64, c fuzz.Continue) { // TODO: uint64's are NOT handled right. *u64 = c.RandUint64() >> 8 }, ) f.Fuzz(item) j, err := meta.TypeAccessor(item) if err != nil { t.Fatalf("Unexpected error %v for %#v", err, item) } j.SetKind("") j.SetAPIVersion("") return item }
func proxyContext(version string) { f := NewFramework("proxy") prefix := "/api/" + version // Port here has to be kept in sync with default kubelet port. It("should proxy logs on node with explicit kubelet port", func() { nodeProxyTest(f, version, ":10250/logs/") }) It("should proxy logs on node", func() { nodeProxyTest(f, version, "/logs/") }) It("should proxy to cadvisor", func() { nodeProxyTest(f, version, ":4194/containers/") }) It("should proxy through a service and a pod", func() { labels := map[string]string{"proxy-service-target": "true"} service, err := f.Client.Services(f.Namespace.Name).Create(&api.Service{ ObjectMeta: api.ObjectMeta{ GenerateName: "proxy-service-", }, Spec: api.ServiceSpec{ Selector: labels, Ports: []api.ServicePort{ { Name: "portname1", Port: 80, TargetPort: util.NewIntOrStringFromString("dest1"), }, { Name: "portname2", Port: 81, TargetPort: util.NewIntOrStringFromInt(162), }, { Name: "tlsportname1", Port: 443, TargetPort: util.NewIntOrStringFromString("tlsdest1"), }, { Name: "tlsportname2", Port: 444, TargetPort: util.NewIntOrStringFromInt(462), }, }, }, }) Expect(err).NotTo(HaveOccurred()) defer func(name string) { err := f.Client.Services(f.Namespace.Name).Delete(name) if err != nil { Logf("Failed deleting service %v: %v", name, err) } }(service.Name) // Make an RC with a single pod. pods := []*api.Pod{} cfg := RCConfig{ Client: f.Client, Image: "gcr.io/google_containers/porter:cd5cb5791ebaa8641955f0e8c2a9bed669b1eaab", Name: service.Name, Namespace: f.Namespace.Name, Replicas: 1, PollInterval: time.Second, Env: map[string]string{ "SERVE_PORT_80": `<a href="/rewriteme">test</a>`, "SERVE_PORT_160": "foo", "SERVE_PORT_162": "bar", "SERVE_TLS_PORT_443": `<a href="/tlsrewriteme">test</a>`, "SERVE_TLS_PORT_460": `tls baz`, "SERVE_TLS_PORT_462": `tls qux`, }, Ports: map[string]int{ "dest1": 160, "dest2": 162, "tlsdest1": 460, "tlsdest2": 462, }, Labels: labels, CreatedPods: &pods, } Expect(RunRC(cfg)).NotTo(HaveOccurred()) defer DeleteRC(f.Client, f.Namespace.Name, cfg.Name) Expect(f.WaitForAnEndpoint(service.Name)).NotTo(HaveOccurred()) // Try proxying through the service and directly to through the pod. svcProxyURL := func(scheme, port string) string { return prefix + "/proxy/namespaces/" + f.Namespace.Name + "/services/" + util.JoinSchemeNamePort(scheme, service.Name, port) } podProxyURL := func(scheme, port string) string { return prefix + "/proxy/namespaces/" + f.Namespace.Name + "/pods/" + util.JoinSchemeNamePort(scheme, pods[0].Name, port) } subresourcePodProxyURL := func(scheme, port string) string { return prefix + "/namespaces/" + f.Namespace.Name + "/pods/" + util.JoinSchemeNamePort(scheme, pods[0].Name, port) + "/proxy" } expectations := map[string]string{ svcProxyURL("", "portname1") + "/": "foo", svcProxyURL("", "portname2") + "/": "bar", svcProxyURL("http", "portname1") + "/": "foo", svcProxyURL("http", "portname2") + "/": "bar", svcProxyURL("https", "tlsportname1") + "/": "tls baz", svcProxyURL("https", "tlsportname2") + "/": "tls qux", podProxyURL("", "80") + "/": `<a href="` + podProxyURL("", "80") + `/rewriteme">test</a>`, podProxyURL("", "160") + "/": "foo", podProxyURL("", "162") + "/": "bar", podProxyURL("http", "80") + "/": `<a href="` + podProxyURL("http", "80") + `/rewriteme">test</a>`, podProxyURL("http", "160") + "/": "foo", podProxyURL("http", "162") + "/": "bar", subresourcePodProxyURL("", "") + "/": `<a href="` + subresourcePodProxyURL("", "") + `/rewriteme">test</a>`, subresourcePodProxyURL("", "80") + "/": `<a href="` + subresourcePodProxyURL("", "80") + `/rewriteme">test</a>`, subresourcePodProxyURL("http", "80") + "/": `<a href="` + subresourcePodProxyURL("http", "80") + `/rewriteme">test</a>`, subresourcePodProxyURL("", "160") + "/": "foo", subresourcePodProxyURL("http", "160") + "/": "foo", subresourcePodProxyURL("", "162") + "/": "bar", subresourcePodProxyURL("http", "162") + "/": "bar", subresourcePodProxyURL("https", "443") + "/": `<a href="` + subresourcePodProxyURL("https", "443") + `/tlsrewriteme">test</a>`, subresourcePodProxyURL("https", "460") + "/": "tls baz", subresourcePodProxyURL("https", "462") + "/": "tls qux", // TODO: below entries don't work, but I believe we should make them work. // svcPrefix + ":80": "foo", // svcPrefix + ":81": "bar", // podPrefix + ":dest1": "foo", // podPrefix + ":dest2": "bar", } wg := sync.WaitGroup{} errors := []string{} errLock := sync.Mutex{} recordError := func(s string) { errLock.Lock() defer errLock.Unlock() errors = append(errors, s) } for i := 0; i < proxyAttempts; i++ { for path, val := range expectations { wg.Add(1) go func(i int, path, val string) { defer wg.Done() body, status, d, err := doProxy(f, path) if err != nil { recordError(fmt.Sprintf("%v: path %v gave error: %v", i, path, err)) return } if status != http.StatusOK { recordError(fmt.Sprintf("%v: path %v gave status: %v", i, path, status)) } if e, a := val, string(body); e != a { recordError(fmt.Sprintf("%v: path %v: wanted %v, got %v", i, path, e, a)) } if d > 15*time.Second { recordError(fmt.Sprintf("%v: path %v took %v > 15s", i, path, d)) } }(i, path, val) // default QPS is 5 time.Sleep(200 * time.Millisecond) } } wg.Wait() if len(errors) != 0 { Fail(strings.Join(errors, "\n")) } }) }
func generate(genericParams map[string]interface{}) (runtime.Object, error) { params := map[string]string{} for key, value := range genericParams { strVal, isString := value.(string) if !isString { return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key) } params[key] = strVal } selectorString, found := params["selector"] if !found || len(selectorString) == 0 { return nil, fmt.Errorf("'selector' is a required parameter.") } selector, err := ParseLabels(selectorString) if err != nil { return nil, err } labelsString, found := params["labels"] var labels map[string]string if found && len(labelsString) > 0 { labels, err = ParseLabels(labelsString) if err != nil { return nil, err } } name, found := params["name"] if !found || len(name) == 0 { name, found = params["default-name"] if !found || len(name) == 0 { return nil, fmt.Errorf("'name' is a required parameter.") } } ports := []api.ServicePort{} servicePortName, found := params["port-name"] if !found { // Leave the port unnamed. servicePortName = "" } // ports takes precedence over port since it will be // specified only when the user hasn't specified a port // via --port and the exposed object has multiple ports. var portString string if portString, found = params["ports"]; !found { portString, found = params["port"] if !found { return nil, fmt.Errorf("'port' is a required parameter.") } } portStringSlice := strings.Split(portString, ",") for i, stillPortString := range portStringSlice { port, err := strconv.Atoi(stillPortString) if err != nil { return nil, err } name := servicePortName // If we are going to assign multiple ports to a service, we need to // generate a different name for each one. if len(portStringSlice) > 1 { name = fmt.Sprintf("port-%d", i+1) } ports = append(ports, api.ServicePort{ Name: name, Port: port, Protocol: api.Protocol(params["protocol"]), }) } service := api.Service{ ObjectMeta: api.ObjectMeta{ Name: name, Labels: labels, }, Spec: api.ServiceSpec{ Selector: selector, Ports: ports, }, } targetPortString, found := params["target-port"] if !found { targetPortString, found = params["container-port"] } if found && len(targetPortString) > 0 { var targetPort util.IntOrString if portNum, err := strconv.Atoi(targetPortString); err != nil { targetPort = util.NewIntOrStringFromString(targetPortString) } else { targetPort = util.NewIntOrStringFromInt(portNum) } // Use the same target-port for every port for i := range service.Spec.Ports { service.Spec.Ports[i].TargetPort = targetPort } } else { // If --target-port or --container-port haven't been specified, this // should be the same as Port for i := range service.Spec.Ports { port := service.Spec.Ports[i].Port service.Spec.Ports[i].TargetPort = util.NewIntOrStringFromInt(port) } } if params["create-external-load-balancer"] == "true" { service.Spec.Type = api.ServiceTypeLoadBalancer } if len(params["external-ip"]) > 0 { service.Spec.ExternalIPs = []string{params["external-ip"]} } if len(params["type"]) != 0 { service.Spec.Type = api.ServiceType(params["type"]) } if service.Spec.Type == api.ServiceTypeLoadBalancer { service.Spec.LoadBalancerIP = params["load-balancer-ip"] } if len(params["session-affinity"]) != 0 { switch api.ServiceAffinity(params["session-affinity"]) { case api.ServiceAffinityNone: service.Spec.SessionAffinity = api.ServiceAffinityNone case api.ServiceAffinityClientIP: service.Spec.SessionAffinity = api.ServiceAffinityClientIP default: return nil, fmt.Errorf("unknown session affinity: %s", params["session-affinity"]) } } return &service, nil }
func generate(params map[string]string) (runtime.Object, error) { selectorString, found := params["selector"] if !found || len(selectorString) == 0 { return nil, fmt.Errorf("'selector' is a required parameter.") } selector, err := ParseLabels(selectorString) if err != nil { return nil, err } labelsString, found := params["labels"] var labels map[string]string if found && len(labelsString) > 0 { labels, err = ParseLabels(labelsString) if err != nil { return nil, err } } name, found := params["name"] if !found || len(name) == 0 { name, found = params["default-name"] if !found || len(name) == 0 { return nil, fmt.Errorf("'name' is a required parameter.") } } portString, found := params["port"] if !found { return nil, fmt.Errorf("'port' is a required parameter.") } port, err := strconv.Atoi(portString) if err != nil { return nil, err } servicePortName, found := params["port-name"] if !found { // Leave the port unnamed. servicePortName = "" } service := api.Service{ ObjectMeta: api.ObjectMeta{ Name: name, Labels: labels, }, Spec: api.ServiceSpec{ Selector: selector, Ports: []api.ServicePort{ { Name: servicePortName, Port: port, Protocol: api.Protocol(params["protocol"]), }, }, }, } targetPort, found := params["target-port"] if !found { targetPort, found = params["container-port"] } if found && len(targetPort) > 0 { if portNum, err := strconv.Atoi(targetPort); err != nil { service.Spec.Ports[0].TargetPort = util.NewIntOrStringFromString(targetPort) } else { service.Spec.Ports[0].TargetPort = util.NewIntOrStringFromInt(portNum) } } else { service.Spec.Ports[0].TargetPort = util.NewIntOrStringFromInt(port) } if params["create-external-load-balancer"] == "true" { service.Spec.Type = api.ServiceTypeLoadBalancer } if len(params["public-ip"]) != 0 { service.Spec.DeprecatedPublicIPs = []string{params["public-ip"]} } if len(params["type"]) != 0 { service.Spec.Type = api.ServiceType(params["type"]) } return &service, nil }
func TestDefaults(t *testing.T) { defaultIntOrString := util.NewIntOrStringFromString("25%") differentIntOrString := util.NewIntOrStringFromInt(5) tests := []struct { original *deployv1.DeploymentConfig expected *deployv1.DeploymentConfig }{ { original: &deployv1.DeploymentConfig{}, expected: &deployv1.DeploymentConfig{ Spec: deployv1.DeploymentConfigSpec{ Strategy: deployv1.DeploymentStrategy{ Type: deployv1.DeploymentStrategyTypeRolling, RollingParams: &deployv1.RollingDeploymentStrategyParams{ UpdatePeriodSeconds: newInt64(deployapi.DefaultRollingUpdatePeriodSeconds), IntervalSeconds: newInt64(deployapi.DefaultRollingIntervalSeconds), TimeoutSeconds: newInt64(deployapi.DefaultRollingTimeoutSeconds), MaxSurge: &defaultIntOrString, MaxUnavailable: &defaultIntOrString, }, }, Triggers: []deployv1.DeploymentTriggerPolicy{ { Type: deployv1.DeploymentTriggerOnConfigChange, }, }, }, }, }, { original: &deployv1.DeploymentConfig{ Spec: deployv1.DeploymentConfigSpec{ Strategy: deployv1.DeploymentStrategy{ Type: deployv1.DeploymentStrategyTypeRecreate, RollingParams: &deployv1.RollingDeploymentStrategyParams{ UpdatePeriodSeconds: newInt64(5), IntervalSeconds: newInt64(6), TimeoutSeconds: newInt64(7), MaxSurge: &differentIntOrString, MaxUnavailable: &differentIntOrString, }, }, Triggers: []deployv1.DeploymentTriggerPolicy{ { Type: deployv1.DeploymentTriggerOnImageChange, }, }, }, }, expected: &deployv1.DeploymentConfig{ Spec: deployv1.DeploymentConfigSpec{ Strategy: deployv1.DeploymentStrategy{ Type: deployv1.DeploymentStrategyTypeRecreate, RollingParams: &deployv1.RollingDeploymentStrategyParams{ UpdatePeriodSeconds: newInt64(5), IntervalSeconds: newInt64(6), TimeoutSeconds: newInt64(7), MaxSurge: &differentIntOrString, MaxUnavailable: &differentIntOrString, }, }, Triggers: []deployv1.DeploymentTriggerPolicy{ { Type: deployv1.DeploymentTriggerOnImageChange, }, }, }, }, }, { original: &deployv1.DeploymentConfig{ Spec: deployv1.DeploymentConfigSpec{ Strategy: deployv1.DeploymentStrategy{ Type: deployv1.DeploymentStrategyTypeRolling, RollingParams: &deployv1.RollingDeploymentStrategyParams{ UpdatePeriodSeconds: newInt64(5), IntervalSeconds: newInt64(6), TimeoutSeconds: newInt64(7), UpdatePercent: newInt(50), }, }, Triggers: []deployv1.DeploymentTriggerPolicy{ { Type: deployv1.DeploymentTriggerOnImageChange, }, }, }, }, expected: &deployv1.DeploymentConfig{ Spec: deployv1.DeploymentConfigSpec{ Strategy: deployv1.DeploymentStrategy{ Type: deployv1.DeploymentStrategyTypeRolling, RollingParams: &deployv1.RollingDeploymentStrategyParams{ UpdatePeriodSeconds: newInt64(5), IntervalSeconds: newInt64(6), TimeoutSeconds: newInt64(7), UpdatePercent: newInt(50), MaxSurge: newIntOrString(util.NewIntOrStringFromString("50%")), MaxUnavailable: newIntOrString(util.NewIntOrStringFromInt(0)), }, }, Triggers: []deployv1.DeploymentTriggerPolicy{ { Type: deployv1.DeploymentTriggerOnImageChange, }, }, }, }, }, { original: &deployv1.DeploymentConfig{ Spec: deployv1.DeploymentConfigSpec{ Strategy: deployv1.DeploymentStrategy{ Type: deployv1.DeploymentStrategyTypeRolling, RollingParams: &deployv1.RollingDeploymentStrategyParams{ UpdatePeriodSeconds: newInt64(5), IntervalSeconds: newInt64(6), TimeoutSeconds: newInt64(7), UpdatePercent: newInt(-25), }, }, Triggers: []deployv1.DeploymentTriggerPolicy{ { Type: deployv1.DeploymentTriggerOnImageChange, }, }, }, }, expected: &deployv1.DeploymentConfig{ Spec: deployv1.DeploymentConfigSpec{ Strategy: deployv1.DeploymentStrategy{ Type: deployv1.DeploymentStrategyTypeRolling, RollingParams: &deployv1.RollingDeploymentStrategyParams{ UpdatePeriodSeconds: newInt64(5), IntervalSeconds: newInt64(6), TimeoutSeconds: newInt64(7), UpdatePercent: newInt(-25), MaxSurge: newIntOrString(util.NewIntOrStringFromInt(0)), MaxUnavailable: newIntOrString(util.NewIntOrStringFromString("25%")), }, }, Triggers: []deployv1.DeploymentTriggerPolicy{ { Type: deployv1.DeploymentTriggerOnImageChange, }, }, }, }, }, } for i, test := range tests { t.Logf("test %d", i) original := test.original expected := test.expected obj2 := roundTrip(t, runtime.Object(original)) got, ok := obj2.(*deployv1.DeploymentConfig) if !ok { t.Errorf("unexpected object: %v", got) t.FailNow() } if !reflect.DeepEqual(got.Spec, expected.Spec) { t.Errorf("got different than expected:\nA:\t%#v\nB:\t%#v\n\nDiff:\n%s\n\n%s", got, expected, util.ObjectDiff(expected, got), util.ObjectGoPrintSideBySide(expected, got)) } } }
func (testServiceGenerator) Generate(genericParams map[string]interface{}) (runtime.Object, error) { params := map[string]string{} for key, value := range genericParams { strVal, isString := value.(string) if !isString { return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key) } params[key] = strVal } labelsString, found := params["labels"] var labels map[string]string var err error if found && len(labelsString) > 0 { labels, err = kubectl.ParseLabels(labelsString) if err != nil { return nil, err } } name, found := params["name"] if !found || len(name) == 0 { name, found = params["default-name"] if !found || len(name) == 0 { return nil, fmt.Errorf("'name' is a required parameter.") } } portString, found := params["port"] if !found { return nil, fmt.Errorf("'port' is a required parameter.") } port, err := strconv.Atoi(portString) if err != nil { return nil, err } servicePortName, found := params["port-name"] if !found { // Leave the port unnamed. servicePortName = "" } service := api.Service{ ObjectMeta: api.ObjectMeta{ Name: name, Labels: labels, }, Spec: api.ServiceSpec{ Ports: []api.ServicePort{ { Name: servicePortName, Port: port, Protocol: api.Protocol(params["protocol"]), }, }, }, } targetPort, found := params["target-port"] if !found { targetPort, found = params["container-port"] } if found && len(targetPort) > 0 { if portNum, err := strconv.Atoi(targetPort); err != nil { service.Spec.Ports[0].TargetPort = util.NewIntOrStringFromString(targetPort) } else { service.Spec.Ports[0].TargetPort = util.NewIntOrStringFromInt(portNum) } } else { service.Spec.Ports[0].TargetPort = util.NewIntOrStringFromInt(port) } if params["create-external-load-balancer"] == "true" { service.Spec.Type = api.ServiceTypeLoadBalancer } if len(params["external-ip"]) > 0 { service.Spec.ExternalIPs = []string{params["external-ip"]} } if len(params["type"]) != 0 { service.Spec.Type = api.ServiceType(params["type"]) } if len(params["session-affinity"]) != 0 { switch api.ServiceAffinity(params["session-affinity"]) { case api.ServiceAffinityNone: service.Spec.SessionAffinity = api.ServiceAffinityNone case api.ServiceAffinityClientIP: service.Spec.SessionAffinity = api.ServiceAffinityClientIP default: return nil, fmt.Errorf("unknown session affinity: %s", params["session-affinity"]) } } return &service, nil }
labels := map[string]string{"foo": "bar"} svc1port := "svc1" svc2port := "svc2" service := &api.Service{ ObjectMeta: api.ObjectMeta{ Name: serviceName, }, Spec: api.ServiceSpec{ Selector: labels, Ports: []api.ServicePort{ { Name: "portname1", Port: 80, TargetPort: util.NewIntOrStringFromString(svc1port), }, { Name: "portname2", Port: 81, TargetPort: util.NewIntOrStringFromString(svc2port), }, }, }, } _, err := c.Services(ns).Create(service) Expect(err).NotTo(HaveOccurred()) port1 := 100 port2 := 101 validateEndpointsOrFail(c, ns, serviceName, map[string][]int{})
// TestValidateRouteBad ensures not specifying a required field results in error and a fully specified // route passes successfully func TestValidateRoute(t *testing.T) { tests := []struct { name string route *api.Route expectedErrors int }{ { name: "No Name", route: &api.Route{ ObjectMeta: kapi.ObjectMeta{ Namespace: "foo", }, Spec: api.RouteSpec{ Host: "host", To: kapi.ObjectReference{ Name: "serviceName", }, }, }, expectedErrors: 1, }, { name: "No namespace", route: &api.Route{ ObjectMeta: kapi.ObjectMeta{ Name: "name", }, Spec: api.RouteSpec{ Host: "host", To: kapi.ObjectReference{ Name: "serviceName", }, }, }, expectedErrors: 1, }, { name: "No host", route: &api.Route{ ObjectMeta: kapi.ObjectMeta{ Name: "name", Namespace: "foo", }, Spec: api.RouteSpec{ To: kapi.ObjectReference{ Name: "serviceName", }, }, }, expectedErrors: 0, }, { name: "Invalid DNS 952 host", route: &api.Route{ ObjectMeta: kapi.ObjectMeta{ Name: "name", Namespace: "foo", }, Spec: api.RouteSpec{ Host: "**", To: kapi.ObjectReference{ Name: "serviceName", }, }, }, expectedErrors: 1, }, { name: "No service name", route: &api.Route{ ObjectMeta: kapi.ObjectMeta{ Name: "name", Namespace: "foo", }, Spec: api.RouteSpec{ Host: "host", }, }, expectedErrors: 1, }, { name: "Zero port", route: &api.Route{ ObjectMeta: kapi.ObjectMeta{ Name: "name", Namespace: "foo", }, Spec: api.RouteSpec{ Host: "www.example.com", To: kapi.ObjectReference{ Name: "serviceName", }, Port: &api.RoutePort{ TargetPort: util.NewIntOrStringFromInt(0), }, }, }, expectedErrors: 1, }, { name: "Empty string port", route: &api.Route{ ObjectMeta: kapi.ObjectMeta{ Name: "name", Namespace: "foo", }, Spec: api.RouteSpec{ Host: "www.example.com", To: kapi.ObjectReference{ Name: "serviceName", }, Port: &api.RoutePort{ TargetPort: util.NewIntOrStringFromString(""), }, }, }, expectedErrors: 1, }, { name: "Valid route", route: &api.Route{ ObjectMeta: kapi.ObjectMeta{ Name: "name", Namespace: "foo", }, Spec: api.RouteSpec{ Host: "www.example.com", To: kapi.ObjectReference{ Name: "serviceName", }, }, }, expectedErrors: 0, }, { name: "Valid route with path", route: &api.Route{ ObjectMeta: kapi.ObjectMeta{ Name: "name", Namespace: "foo", }, Spec: api.RouteSpec{ Host: "www.example.com", To: kapi.ObjectReference{ Name: "serviceName", }, Path: "/test", }, }, expectedErrors: 0, }, { name: "Invalid route with path", route: &api.Route{ ObjectMeta: kapi.ObjectMeta{ Name: "name", Namespace: "foo", }, Spec: api.RouteSpec{ Host: "www.example.com", To: kapi.ObjectReference{ Name: "serviceName", }, Path: "test", }, }, expectedErrors: 1, }, } for _, tc := range tests { errs := ValidateRoute(tc.route) if len(errs) != tc.expectedErrors { t.Errorf("Test case %s expected %d error(s), got %d. %v", tc.name, tc.expectedErrors, len(errs), errs) } } }
func TestValidateDeployment(t *testing.T) { successCases := []*experimental.Deployment{ validDeployment(), } for _, successCase := range successCases { if errs := ValidateDeployment(successCase); len(errs) != 0 { t.Errorf("expected success: %v", errs) } } errorCases := map[string]*experimental.Deployment{} errorCases["metadata.name: required value"] = &experimental.Deployment{ ObjectMeta: api.ObjectMeta{ Namespace: api.NamespaceDefault, }, } // selector should match the labels in pod template. invalidSelectorDeployment := validDeployment() invalidSelectorDeployment.Spec.Selector = map[string]string{ "name": "def", } errorCases["selector does not match labels"] = invalidSelectorDeployment // RestartPolicy should be always. invalidRestartPolicyDeployment := validDeployment() invalidRestartPolicyDeployment.Spec.Template.Spec.RestartPolicy = api.RestartPolicyNever errorCases["unsupported value 'Never'"] = invalidRestartPolicyDeployment // invalid unique label key. invalidUniqueLabelDeployment := validDeployment() invalidUniqueLabelDeployment.Spec.UniqueLabelKey = "abc/def/ghi" errorCases["spec.uniqueLabel: invalid value"] = invalidUniqueLabelDeployment // rollingUpdate should be nil for recreate. invalidRecreateDeployment := validDeployment() invalidRecreateDeployment.Spec.Strategy = experimental.DeploymentStrategy{ Type: experimental.DeploymentRecreate, RollingUpdate: &experimental.RollingUpdateDeployment{}, } errorCases["rollingUpdate should be nil when strategy type is Recreate"] = invalidRecreateDeployment // MaxSurge should be in the form of 20%. invalidMaxSurgeDeployment := validDeployment() invalidMaxSurgeDeployment.Spec.Strategy = experimental.DeploymentStrategy{ Type: experimental.DeploymentRollingUpdate, RollingUpdate: &experimental.RollingUpdateDeployment{ MaxSurge: util.NewIntOrStringFromString("20Percent"), }, } errorCases["value should be int(5) or percentage(5%)"] = invalidMaxSurgeDeployment // MaxSurge and MaxUnavailable cannot both be zero. invalidRollingUpdateDeployment := validDeployment() invalidRollingUpdateDeployment.Spec.Strategy = experimental.DeploymentStrategy{ Type: experimental.DeploymentRollingUpdate, RollingUpdate: &experimental.RollingUpdateDeployment{ MaxSurge: util.NewIntOrStringFromString("0%"), MaxUnavailable: util.NewIntOrStringFromInt(0), }, } errorCases["cannot be 0 when maxSurge is 0 as well"] = invalidRollingUpdateDeployment // MaxUnavailable should not be more than 100%. invalidMaxUnavailableDeployment := validDeployment() invalidMaxUnavailableDeployment.Spec.Strategy = experimental.DeploymentStrategy{ Type: experimental.DeploymentRollingUpdate, RollingUpdate: &experimental.RollingUpdateDeployment{ MaxUnavailable: util.NewIntOrStringFromString("110%"), }, } errorCases["should not be more than 100%"] = invalidMaxUnavailableDeployment for k, v := range errorCases { errs := ValidateDeployment(v) if len(errs) == 0 { t.Errorf("expected failure for %s", k) } else if !strings.Contains(errs[0].Error(), k) { t.Errorf("unexpected error: %v, expected: %s", errs[0], k) } } }
func addDefaultingFuncs() { api.Scheme.AddDefaultingFuncs( func(obj *ReplicationController) { var labels map[string]string if obj.Spec.Template != nil { labels = obj.Spec.Template.Labels } // TODO: support templates defined elsewhere when we support them in the API if labels != nil { if len(obj.Spec.Selector) == 0 { obj.Spec.Selector = labels } if len(obj.Labels) == 0 { obj.Labels = labels } } if obj.Spec.Replicas == nil { obj.Spec.Replicas = new(int) *obj.Spec.Replicas = 1 } }, func(obj *Daemon) { var labels map[string]string if obj.Spec.Template != nil { labels = obj.Spec.Template.Labels } // TODO: support templates defined elsewhere when we support them in the API if labels != nil { if len(obj.Spec.Selector) == 0 { obj.Spec.Selector = labels } if len(obj.Labels) == 0 { obj.Labels = labels } } }, func(obj *Volume) { if util.AllPtrFieldsNil(&obj.VolumeSource) { obj.VolumeSource = VolumeSource{ EmptyDir: &EmptyDirVolumeSource{}, } } }, func(obj *ContainerPort) { if obj.Protocol == "" { obj.Protocol = ProtocolTCP } }, func(obj *Container) { if obj.ImagePullPolicy == "" { // TODO(dchen1107): Move ParseImageName code to pkg/util parts := strings.Split(obj.Image, ":") // Check image tag if parts[len(parts)-1] == "latest" { obj.ImagePullPolicy = PullAlways } else { obj.ImagePullPolicy = PullIfNotPresent } } if obj.TerminationMessagePath == "" { obj.TerminationMessagePath = TerminationMessagePathDefault } }, func(obj *ServiceSpec) { if obj.SessionAffinity == "" { obj.SessionAffinity = ServiceAffinityNone } if obj.Type == "" { obj.Type = ServiceTypeClusterIP } for i := range obj.Ports { sp := &obj.Ports[i] if sp.Protocol == "" { sp.Protocol = ProtocolTCP } if sp.TargetPort == util.NewIntOrStringFromInt(0) || sp.TargetPort == util.NewIntOrStringFromString("") { sp.TargetPort = util.NewIntOrStringFromInt(sp.Port) } } }, func(obj *PodSpec) { if obj.DNSPolicy == "" { obj.DNSPolicy = DNSClusterFirst } if obj.RestartPolicy == "" { obj.RestartPolicy = RestartPolicyAlways } if obj.HostNetwork { defaultHostNetworkPorts(&obj.Containers) } }, func(obj *Probe) { if obj.TimeoutSeconds == 0 { obj.TimeoutSeconds = 1 } }, func(obj *Secret) { if obj.Type == "" { obj.Type = SecretTypeOpaque } }, func(obj *PersistentVolume) { if obj.Status.Phase == "" { obj.Status.Phase = VolumePending } if obj.Spec.PersistentVolumeReclaimPolicy == "" { obj.Spec.PersistentVolumeReclaimPolicy = PersistentVolumeReclaimRetain } }, func(obj *PersistentVolumeClaim) { if obj.Status.Phase == "" { obj.Status.Phase = ClaimPending } }, func(obj *Endpoints) { for i := range obj.Subsets { ss := &obj.Subsets[i] for i := range ss.Ports { ep := &ss.Ports[i] if ep.Protocol == "" { ep.Protocol = ProtocolTCP } } } }, func(obj *HTTPGetAction) { if obj.Path == "" { obj.Path = "/" } if obj.Scheme == "" { obj.Scheme = URISchemeHTTP } }, func(obj *NamespaceStatus) { if obj.Phase == "" { obj.Phase = NamespaceActive } }, func(obj *Node) { if obj.Spec.ExternalID == "" { obj.Spec.ExternalID = obj.Name } }, func(obj *ObjectFieldSelector) { if obj.APIVersion == "" { obj.APIVersion = "v1" } }, ) }
func TestFindPort(t *testing.T) { testCases := []struct { name string containers []api.Container port util.IntOrString expected int pass bool }{{ name: "valid int, no ports", containers: []api.Container{{}}, port: util.NewIntOrStringFromInt(93), expected: 93, pass: true, }, { name: "valid int, with ports", containers: []api.Container{{Ports: []api.ContainerPort{{ Name: "", ContainerPort: 11, Protocol: "TCP", }, { Name: "p", ContainerPort: 22, Protocol: "TCP", }}}}, port: util.NewIntOrStringFromInt(93), expected: 93, pass: true, }, { name: "valid str, no ports", containers: []api.Container{{}}, port: util.NewIntOrStringFromString("p"), expected: 0, pass: false, }, { name: "valid str, one ctr with ports", containers: []api.Container{{Ports: []api.ContainerPort{{ Name: "", ContainerPort: 11, Protocol: "UDP", }, { Name: "p", ContainerPort: 22, Protocol: "TCP", }, { Name: "q", ContainerPort: 33, Protocol: "TCP", }}}}, port: util.NewIntOrStringFromString("q"), expected: 33, pass: true, }, { name: "valid str, two ctr with ports", containers: []api.Container{{}, {Ports: []api.ContainerPort{{ Name: "", ContainerPort: 11, Protocol: "UDP", }, { Name: "p", ContainerPort: 22, Protocol: "TCP", }, { Name: "q", ContainerPort: 33, Protocol: "TCP", }}}}, port: util.NewIntOrStringFromString("q"), expected: 33, pass: true, }} for _, tc := range testCases { port, err := findPort(&api.Pod{Spec: api.PodSpec{Containers: tc.containers}}, &api.ServicePort{Protocol: "TCP", TargetPort: tc.port}) if err != nil && tc.pass { t.Errorf("unexpected error for %s: %v", tc.name, err) } if err == nil && !tc.pass { t.Errorf("unexpected non-error for %s: %d", tc.name, port) } if port != tc.expected { t.Errorf("wrong result for %s: expected %d, got %d", tc.name, tc.expected, port) } } }
func fuzzInternalObject(t *testing.T, forVersion string, item runtime.Object, seed int64) runtime.Object { f := apitesting.FuzzerFor(t, forVersion, rand.NewSource(seed)) f.Funcs( // Roles and RoleBindings maps are never nil func(j *authorizationapi.Policy, c fuzz.Continue) { j.Roles = make(map[string]*authorizationapi.Role) }, func(j *authorizationapi.PolicyBinding, c fuzz.Continue) { j.RoleBindings = make(map[string]*authorizationapi.RoleBinding) }, func(j *authorizationapi.ClusterPolicy, c fuzz.Continue) { j.Roles = make(map[string]*authorizationapi.ClusterRole) }, func(j *authorizationapi.ClusterPolicyBinding, c fuzz.Continue) { j.RoleBindings = make(map[string]*authorizationapi.ClusterRoleBinding) }, func(j *authorizationapi.RoleBinding, c fuzz.Continue) { c.FuzzNoCustom(j) for i := range j.Subjects { kinds := []string{authorizationapi.UserKind, authorizationapi.SystemUserKind, authorizationapi.GroupKind, authorizationapi.SystemGroupKind, authorizationapi.ServiceAccountKind} j.Subjects[i].Kind = kinds[c.Intn(len(kinds))] switch j.Subjects[i].Kind { case authorizationapi.UserKind: j.Subjects[i].Namespace = "" if valid, _ := uservalidation.ValidateUserName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("validusername%d", i) } case authorizationapi.GroupKind: j.Subjects[i].Namespace = "" if valid, _ := uservalidation.ValidateGroupName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("validgroupname%d", i) } case authorizationapi.ServiceAccountKind: if valid, _ := validation.ValidateNamespaceName(j.Subjects[i].Namespace, false); !valid { j.Subjects[i].Namespace = fmt.Sprintf("sanamespacehere%d", i) } if valid, _ := validation.ValidateServiceAccountName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("sanamehere%d", i) } case authorizationapi.SystemUserKind, authorizationapi.SystemGroupKind: j.Subjects[i].Namespace = "" j.Subjects[i].Name = ":" + j.Subjects[i].Name } j.Subjects[i].UID = types.UID("") j.Subjects[i].APIVersion = "" j.Subjects[i].ResourceVersion = "" j.Subjects[i].FieldPath = "" } }, func(j *authorizationapi.ClusterRoleBinding, c fuzz.Continue) { c.FuzzNoCustom(j) for i := range j.Subjects { kinds := []string{authorizationapi.UserKind, authorizationapi.SystemUserKind, authorizationapi.GroupKind, authorizationapi.SystemGroupKind, authorizationapi.ServiceAccountKind} j.Subjects[i].Kind = kinds[c.Intn(len(kinds))] switch j.Subjects[i].Kind { case authorizationapi.UserKind: j.Subjects[i].Namespace = "" if valid, _ := uservalidation.ValidateUserName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("validusername%d", i) } case authorizationapi.GroupKind: j.Subjects[i].Namespace = "" if valid, _ := uservalidation.ValidateGroupName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("validgroupname%d", i) } case authorizationapi.ServiceAccountKind: if valid, _ := validation.ValidateNamespaceName(j.Subjects[i].Namespace, false); !valid { j.Subjects[i].Namespace = fmt.Sprintf("sanamespacehere%d", i) } if valid, _ := validation.ValidateServiceAccountName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("sanamehere%d", i) } case authorizationapi.SystemUserKind, authorizationapi.SystemGroupKind: j.Subjects[i].Namespace = "" j.Subjects[i].Name = ":" + j.Subjects[i].Name } j.Subjects[i].UID = types.UID("") j.Subjects[i].APIVersion = "" j.Subjects[i].ResourceVersion = "" j.Subjects[i].FieldPath = "" } }, func(j *template.Template, c fuzz.Continue) { c.Fuzz(&j.ObjectMeta) c.Fuzz(&j.Parameters) // TODO: replace with structured type definition j.Objects = []runtime.Object{} }, func(j *image.Image, c fuzz.Continue) { c.Fuzz(&j.ObjectMeta) c.Fuzz(&j.DockerImageMetadata) j.DockerImageMetadata.APIVersion = "" j.DockerImageMetadata.Kind = "" j.DockerImageMetadataVersion = []string{"pre012", "1.0"}[c.Rand.Intn(2)] j.DockerImageReference = c.RandString() }, func(j *image.ImageStreamMapping, c fuzz.Continue) { c.FuzzNoCustom(j) j.DockerImageRepository = "" }, func(j *image.ImageStreamImage, c fuzz.Continue) { c.Fuzz(&j.Image) // because we de-embedded Image from ImageStreamImage, in order to round trip // successfully, the ImageStreamImage's ObjectMeta must match the Image's. j.ObjectMeta = j.Image.ObjectMeta }, func(j *image.ImageStreamTag, c fuzz.Continue) { c.Fuzz(&j.Image) // because we de-embedded Image from ImageStreamTag, in order to round trip // successfully, the ImageStreamTag's ObjectMeta must match the Image's. j.ObjectMeta = j.Image.ObjectMeta }, func(j *image.TagReference, c fuzz.Continue) { c.FuzzNoCustom(j) if j.From != nil { specs := []string{"", "ImageStreamTag", "ImageStreamImage"} j.From.Kind = specs[c.Intn(len(specs))] } }, func(j *build.SourceBuildStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) j.From.Kind = "ImageStreamTag" j.From.Name = "image:tag" j.From.APIVersion = "" j.From.ResourceVersion = "" j.From.FieldPath = "" }, func(j *build.CustomBuildStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) j.From.Kind = "ImageStreamTag" j.From.Name = "image:tag" j.From.APIVersion = "" j.From.ResourceVersion = "" j.From.FieldPath = "" }, func(j *build.DockerBuildStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) j.From.Kind = "ImageStreamTag" j.From.Name = "image:tag" j.From.APIVersion = "" j.From.ResourceVersion = "" j.From.FieldPath = "" }, func(j *build.BuildOutput, c fuzz.Continue) { c.FuzzNoCustom(j) if j.To != nil && (len(j.To.Kind) == 0 || j.To.Kind == "ImageStream") { j.To.Kind = "ImageStreamTag" } if j.To != nil && strings.Contains(j.To.Name, ":") { j.To.Name = strings.Replace(j.To.Name, ":", "-", -1) } }, func(j *route.RouteSpec, c fuzz.Continue) { c.FuzzNoCustom(j) j.To = api.ObjectReference{ Kind: "Service", Name: j.To.Name, } }, func(j *deploy.DeploymentConfig, c fuzz.Continue) { c.FuzzNoCustom(j) j.Triggers = []deploy.DeploymentTriggerPolicy{{Type: deploy.DeploymentTriggerOnConfigChange}} }, func(j *deploy.DeploymentStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) strategyTypes := []deploy.DeploymentStrategyType{deploy.DeploymentStrategyTypeRecreate, deploy.DeploymentStrategyTypeRolling, deploy.DeploymentStrategyTypeCustom} j.Type = strategyTypes[c.Rand.Intn(len(strategyTypes))] switch j.Type { case deploy.DeploymentStrategyTypeRolling: params := &deploy.RollingDeploymentStrategyParams{} randInt64 := func() *int64 { p := int64(c.RandUint64()) return &p } params.TimeoutSeconds = randInt64() params.IntervalSeconds = randInt64() params.UpdatePeriodSeconds = randInt64() if c.RandBool() { params.MaxUnavailable = util.NewIntOrStringFromInt(int(c.RandUint64())) params.MaxSurge = util.NewIntOrStringFromInt(int(c.RandUint64())) } else { params.MaxSurge = util.NewIntOrStringFromString(fmt.Sprintf("%d%%", c.RandUint64())) params.MaxUnavailable = util.NewIntOrStringFromString(fmt.Sprintf("%d%%", c.RandUint64())) } j.RollingParams = params default: j.RollingParams = nil } }, func(j *deploy.DeploymentCauseImageTrigger, c fuzz.Continue) { c.FuzzNoCustom(j) specs := []string{"", "a/b", "a/b/c", "a:5000/b/c", "a/b", "a/b"} tags := []string{"", "stuff", "other"} j.RepositoryName = specs[c.Intn(len(specs))] if len(j.RepositoryName) > 0 { j.Tag = tags[c.Intn(len(tags))] } else { j.Tag = "" } }, func(j *deploy.DeploymentTriggerImageChangeParams, c fuzz.Continue) { c.FuzzNoCustom(j) specs := []string{"a/b", "a/b/c", "a:5000/b/c", "a/b:latest", "a/b@test"} j.From.Kind = "DockerImage" j.From.Name = specs[c.Intn(len(specs))] if ref, err := image.ParseDockerImageReference(j.From.Name); err == nil { j.Tag = ref.Tag ref.Tag, ref.ID = "", "" j.RepositoryName = ref.String() } }, func(j *runtime.EmbeddedObject, c fuzz.Continue) { // runtime.EmbeddedObject causes a panic inside of fuzz because runtime.Object isn't handled. }, func(t *time.Time, c fuzz.Continue) { // This is necessary because the standard fuzzed time.Time object is // completely nil, but when JSON unmarshals dates it fills in the // unexported loc field with the time.UTC object, resulting in // reflect.DeepEqual returning false in the round trip tests. We solve it // by using a date that will be identical to the one JSON unmarshals. *t = time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC) }, func(u64 *uint64, c fuzz.Continue) { // TODO: uint64's are NOT handled right. *u64 = c.RandUint64() >> 8 }, ) f.Fuzz(item) j, err := meta.TypeAccessor(item) if err != nil { t.Fatalf("Unexpected error %v for %#v", err, item) } j.SetKind("") j.SetAPIVersion("") return item }