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 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: ports[0], TargetPort: util.NewIntOrStringFromInt(ports[0])}, {Port: ports[1], TargetPort: util.NewIntOrStringFromInt(ports[1])}, {Port: ports[2], 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) } }
svc1port := "svc1" svc2port := "svc2" By("creating service " + serviceName + " in namespace " + ns) 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, PortsByPodName{})
func TestValidateDeployment(t *testing.T) { successCases := []*extensions.Deployment{ validDeployment(), } for _, successCase := range successCases { if errs := ValidateDeployment(successCase); len(errs) != 0 { t.Errorf("expected success: %v", errs) } } errorCases := map[string]*extensions.Deployment{} errorCases["metadata.name: required value"] = &extensions.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 = extensions.DeploymentStrategy{ Type: extensions.RecreateDeploymentStrategyType, RollingUpdate: &extensions.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 = extensions.DeploymentStrategy{ Type: extensions.RollingUpdateDeploymentStrategyType, RollingUpdate: &extensions.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 = extensions.DeploymentStrategy{ Type: extensions.RollingUpdateDeploymentStrategyType, RollingUpdate: &extensions.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 = extensions.DeploymentStrategy{ Type: extensions.RollingUpdateDeploymentStrategyType, RollingUpdate: &extensions.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 (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 }
// 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 *unversioned.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 = unversioned.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 *unversioned.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(s *api.PodSpec, c fuzz.Continue) { c.FuzzNoCustom(s) // has a default value ttl := int64(30) if c.RandBool() { ttl = int64(c.Uint32()) } s.TerminationGracePeriodSeconds = &ttl c.Fuzz(s.SecurityContext) if s.SecurityContext == nil { s.SecurityContext = new(api.PodSecurityContext) } }, 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 *extensions.DeploymentStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) // fuzz self without calling this function again // Ensure that strategyType is one of valid values. strategyTypes := []extensions.DeploymentStrategyType{extensions.RecreateDeploymentStrategyType, extensions.RollingUpdateDeploymentStrategyType} j.Type = strategyTypes[c.Rand.Intn(len(strategyTypes))] if j.Type != extensions.RollingUpdateDeploymentStrategyType { j.RollingUpdate = nil } else { rollingUpdate := extensions.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 *extensions.JobSpec, c fuzz.Continue) { c.FuzzNoCustom(j) // fuzz self without calling this function again completions := c.Rand.Int() parallelism := c.Rand.Int() j.Completions = &completions j.Parallelism = ¶llelism }, 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 if c.RandBool() { priv := c.RandBool() sc.Privileged = &priv } if c.RandBool() { 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 *extensions.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" }, func(s *extensions.HorizontalPodAutoscalerSpec, c fuzz.Continue) { c.FuzzNoCustom(s) // fuzz self without calling this function again minReplicas := c.Rand.Int() s.MinReplicas = &minReplicas s.CPUUtilization = &extensions.CPUTargetUtilization{TargetPercentage: int(c.RandUint64())} }, ) return f }
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 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 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 [Conformance]", func() { nodeProxyTest(f, version, ":10250/logs/") }) It("should proxy logs on node [Conformance]", func() { nodeProxyTest(f, version, "/logs/") }) It("should proxy to cadvisor [Conformance]", func() { nodeProxyTest(f, version, ":4194/containers/") }) It("should proxy through a service and a pod [Conformance]", 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 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 TestGenerateService(t *testing.T) { tests := []struct { generator Generator params map[string]interface{} expected api.Service }{ { generator: ServiceGeneratorV2{}, params: map[string]interface{}{ "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]interface{}{ "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]interface{}{ "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]interface{}{ "selector": "foo=bar,baz=blah", "name": "test", "port": "80", "protocol": "UDP", "container-port": "foobar", "external-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"), }, }, ExternalIPs: []string{"1.2.3.4"}, }, }, }, { generator: ServiceGeneratorV2{}, params: map[string]interface{}{ "selector": "foo=bar,baz=blah", "name": "test", "port": "80", "protocol": "UDP", "container-port": "foobar", "external-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, ExternalIPs: []string{"1.2.3.4"}, }, }, }, { generator: ServiceGeneratorV2{}, params: map[string]interface{}{ "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]interface{}{ "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]interface{}{ "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]interface{}{ "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, }, }, }, { generator: ServiceGeneratorV1{}, params: map[string]interface{}{ "selector": "foo=bar", "name": "test", "ports": "80,443", "protocol": "TCP", "container-port": "foobar", }, expected: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "test", }, Spec: api.ServiceSpec{ Selector: map[string]string{ "foo": "bar", }, Ports: []api.ServicePort{ { Name: "port-1", Port: 80, Protocol: api.ProtocolTCP, TargetPort: util.NewIntOrStringFromString("foobar"), }, { Name: "port-2", Port: 443, Protocol: api.ProtocolTCP, TargetPort: util.NewIntOrStringFromString("foobar"), }, }, }, }, }, { generator: ServiceGeneratorV2{}, params: map[string]interface{}{ "selector": "foo=bar", "name": "test", "ports": "80,443", "protocol": "UDP", "target-port": "1234", }, expected: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "test", }, Spec: api.ServiceSpec{ Selector: map[string]string{ "foo": "bar", }, Ports: []api.ServicePort{ { Name: "port-1", Port: 80, Protocol: api.ProtocolUDP, TargetPort: util.NewIntOrStringFromInt(1234), }, { Name: "port-2", Port: 443, Protocol: api.ProtocolUDP, TargetPort: util.NewIntOrStringFromInt(1234), }, }, }, }, }, { generator: ServiceGeneratorV2{}, params: map[string]interface{}{ "selector": "foo=bar", "name": "test", "ports": "80,443", "protocol": "TCP", }, expected: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "test", }, Spec: api.ServiceSpec{ Selector: map[string]string{ "foo": "bar", }, Ports: []api.ServicePort{ { Name: "port-1", Port: 80, Protocol: api.ProtocolTCP, TargetPort: util.NewIntOrStringFromInt(80), }, { Name: "port-2", Port: 443, Protocol: api.ProtocolTCP, TargetPort: util.NewIntOrStringFromInt(443), }, }, }, }, }, } 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) } } }
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 *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" } }, ) }
// 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()) } } }
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 *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 *Pod) { // If limits are specified, but requests are not, default requests to limits // This is done here rather than a more specific defaulting pass on ResourceRequirements // because we only want this defaulting semantic to take place on a Pod and not a PodTemplate for i := range obj.Spec.Containers { // set requests to limits if requests are not specified, but limits are if obj.Spec.Containers[i].Resources.Limits != nil { if obj.Spec.Containers[i].Resources.Requests == nil { obj.Spec.Containers[i].Resources.Requests = make(ResourceList) } for key, value := range obj.Spec.Containers[i].Resources.Limits { if _, exists := obj.Spec.Containers[i].Resources.Requests[key]; !exists { obj.Spec.Containers[i].Resources.Requests[key] = *(value.Copy()) } } } } }, func(obj *PodSpec) { if obj.DNSPolicy == "" { obj.DNSPolicy = DNSClusterFirst } if obj.RestartPolicy == "" { obj.RestartPolicy = RestartPolicyAlways } if obj.HostNetwork { defaultHostNetworkPorts(&obj.Containers) } if obj.SecurityContext == nil { obj.SecurityContext = &PodSecurityContext{} } 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 *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 *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()) } } } }, ) }