diff --git a/cmd/kube-controller-manager/controller-manager.go b/cmd/kube-controller-manager/controller-manager.go index ceb7e487e4b..884d1a8f8ea 100644 --- a/cmd/kube-controller-manager/controller-manager.go +++ b/cmd/kube-controller-manager/controller-manager.go @@ -22,20 +22,19 @@ package main import ( "flag" - "math" "net" "net/http" "strconv" "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" minionControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/controller" replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" - "github.com/GoogleCloudPlatform/kubernetes/pkg/resources" "github.com/GoogleCloudPlatform/kubernetes/pkg/service" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag" @@ -51,8 +50,9 @@ var ( minionRegexp = flag.String("minion_regexp", "", "If non empty, and -cloud_provider is specified, a regular expression for matching minion VMs.") machineList util.StringList // TODO: Discover these by pinging the host machines, and rip out these flags. + // TODO: in the meantime, use resource.QuantityFlag() instead of these nodeMilliCPU = flag.Int64("node_milli_cpu", 1000, "The amount of MilliCPU provisioned on each node") - nodeMemory = flag.Int64("node_memory", 3*1024*1024*1024, "The amount of memory (in bytes) provisioned on each node") + nodeMemory = resource.QuantityFlag("node_memory", "3Gi", "The amount of memory (in bytes) provisioned on each node") ) func init() { @@ -90,18 +90,6 @@ func main() { glog.Fatalf("Invalid API configuration: %v", err) } - if int64(int(*nodeMilliCPU)) != *nodeMilliCPU { - glog.Warningf("node_milli_cpu is too big for platform. Clamping: %d -> %d", - *nodeMilliCPU, math.MaxInt32) - *nodeMilliCPU = math.MaxInt32 - } - - if int64(int(*nodeMemory)) != *nodeMemory { - glog.Warningf("node_memory is too big for platform. Clamping: %d -> %d", - *nodeMemory, math.MaxInt32) - *nodeMemory = math.MaxInt32 - } - go http.ListenAndServe(net.JoinHostPort(address.String(), strconv.Itoa(*port)), nil) endpoints := service.NewEndpointController(kubeClient) @@ -113,8 +101,8 @@ func main() { cloud := cloudprovider.InitCloudProvider(*cloudProvider, *cloudConfigFile) nodeResources := &api.NodeResources{ Capacity: api.ResourceList{ - resources.CPU: util.NewIntOrStringFromInt(int(*nodeMilliCPU)), - resources.Memory: util.NewIntOrStringFromInt(int(*nodeMemory)), + api.ResourceCPU: *resource.NewMilliQuantity(*nodeMilliCPU, resource.DecimalSI), + api.ResourceMemory: *nodeMemory, }, } minionController := minionControllerPkg.NewMinionController(cloud, *minionRegexp, machineList, nodeResources, kubeClient) diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index a593dde3d90..5ee4fa1b010 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -18,6 +18,28 @@ package api import ( "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" + "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" +) + +// Semantic can do semantic deep equality checks for api objects. +// Example: api.Semantic.DeepEqual(aPod, aPodWithNonNilButEmptyMaps) == true +var Semantic = conversion.EqualitiesOrDie( + func(a, b resource.Quantity) bool { + // Ignore formatting, only care that numeric value stayed the same. + // TODO: if we decide it's important, after we drop v1beta1/2, we + // could start comparing format. + // + // Uninitialized quantities are equivilent to 0 quantities. + if a.Amount == nil && b.MilliValue() == 0 { + return true + } + if b.Amount == nil && a.MilliValue() == 0 { + return true + } + return a.Amount.Cmp(b.Amount) == 0 + }, ) // TODO: Address these per #1502 diff --git a/pkg/api/latest/latest_test.go b/pkg/api/latest/latest_test.go index bedb8a1fcae..56ae1a26e50 100644 --- a/pkg/api/latest/latest_test.go +++ b/pkg/api/latest/latest_test.go @@ -18,7 +18,6 @@ package latest import ( "encoding/json" - "reflect" "strconv" "testing" @@ -167,7 +166,7 @@ func TestInternalRoundTrip(t *testing.T) { continue } - if !reflect.DeepEqual(obj, actual) { + if !internal.Semantic.DeepEqual(obj, actual) { t.Errorf("%s: diff %s", k, util.ObjectDiff(obj, actual)) } } diff --git a/pkg/api/resource/quantity.go b/pkg/api/resource/quantity.go index 3b04fcd91c6..4ae4d38148c 100644 --- a/pkg/api/resource/quantity.go +++ b/pkg/api/resource/quantity.go @@ -102,14 +102,14 @@ const ( DecimalSI = Format("DecimalSI") // e.g., 12M (12 * 10^6) ) -// ParseOrDie turns the given string into a quantity or panics; for tests +// MustParse turns the given string into a quantity or panics; for tests // or others cases where you know the string is valid. -func ParseOrDie(str string) *Quantity { +func MustParse(str string) Quantity { q, err := ParseQuantity(str) if err != nil { panic(fmt.Errorf("cannot parse '%v': %v", str, err)) } - return q + return *q } const ( @@ -148,7 +148,7 @@ var ( // The maximum value we can represent milli-units for. // Compare with the return value of Quantity.Value() to // see if it's safe to use Quantity.MilliValue(). - MaxMilliValue = ((1 << 63) - 1) / 1000 + MaxMilliValue = int64(((1 << 63) - 1) / 1000) ) // ParseQuantity turns str into a Quantity, or returns an error. @@ -403,10 +403,7 @@ func (qf qFlag) String() string { // QuantityFlag is a helper that makes a quantity flag (using standard flag package). // Will panic if defaultValue is not a valid quantity. func QuantityFlag(flagName, defaultValue, description string) *Quantity { - q, err := ParseQuantity(defaultValue) - if err != nil { - panic(fmt.Errorf("can't use %v as a quantity: %v", defaultValue, err)) - } - flag.Var(qFlag{q}, flagName, description) - return q + q := MustParse(defaultValue) + flag.Var(qFlag{&q}, flagName, description) + return &q } diff --git a/pkg/api/resource/quantity_example_test.go b/pkg/api/resource/quantity_example_test.go index 5442cb546c4..f9cbe862f5b 100644 --- a/pkg/api/resource/quantity_example_test.go +++ b/pkg/api/resource/quantity_example_test.go @@ -38,17 +38,17 @@ func ExampleFormat() { // cores = 5300m } -func ExampleParseOrDie() { - memorySize := resource.ParseOrDie("5Gi") +func ExampleMustParse() { + memorySize := resource.MustParse("5Gi") fmt.Printf("memorySize = %v (%v)\n", memorySize.Value(), memorySize.Format) - diskSize := resource.ParseOrDie("5G") + diskSize := resource.MustParse("5G") fmt.Printf("diskSize = %v (%v)\n", diskSize.Value(), diskSize.Format) - cores := resource.ParseOrDie("5300m") + cores := resource.MustParse("5300m") fmt.Printf("milliCores = %v (%v)\n", cores.MilliValue(), cores.Format) - cores2 := resource.ParseOrDie("5.4") + cores2 := resource.MustParse("5.4") fmt.Printf("milliCores = %v (%v)\n", cores2.MilliValue(), cores2.Format) // Output: diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index 5c79980e154..46330c4e10d 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -27,12 +27,15 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + docker "github.com/fsouza/go-dockerclient" fuzz "github.com/google/gofuzz" + "speter.net/go/exp/math/dec/inf" ) var fuzzIters = flag.Int("fuzz_iters", 40, "How many fuzzing iterations to do.") @@ -136,6 +139,16 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( 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 runTest(t *testing.T, codec runtime.Codec, source runtime.Object) { @@ -159,7 +172,7 @@ func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) { t.Errorf("0: %v: %v\nCodec: %v\nData: %s\nSource: %#v", name, err, codec, string(data), source) return } - if !reflect.DeepEqual(source, obj2) { + if !api.Semantic.DeepEqual(source, obj2) { t.Errorf("1: %v: diff: %v\nCodec: %v\nData: %s\nSource: %#v", name, util.ObjectGoPrintDiff(source, obj2), codec, string(data), source) return } @@ -170,7 +183,7 @@ func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) { t.Errorf("2: %v: %v", name, err) return } - if !reflect.DeepEqual(source, obj3) { + if !api.Semantic.DeepEqual(source, obj3) { t.Errorf("3: %v: diff: %v\nCodec: %v", name, util.ObjectDiff(source, obj3), codec) return } @@ -244,7 +257,7 @@ func TestEncode_Ptr(t *testing.T) { if _, ok := obj2.(*api.Pod); !ok { t.Fatalf("Got wrong type") } - if !reflect.DeepEqual(obj2, pod) { + if !api.Semantic.DeepEqual(obj2, pod) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", &pod, obj2) } } diff --git a/pkg/api/types.go b/pkg/api/types.go index d7c0e6e3cf6..ebb32586ede 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -17,6 +17,7 @@ limitations under the License. package api import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -309,12 +310,12 @@ type Container struct { Ports []Port `json:"ports,omitempty"` Env []EnvVar `json:"env,omitempty"` // Optional: Defaults to unlimited. - Memory int `json:"memory,omitempty"` + Memory resource.Quantity `json:"memory,omitempty"` // Optional: Defaults to unlimited. - CPU int `json:"cpu,omitempty"` - VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` - LivenessProbe *LivenessProbe `json:"livenessProbe,omitempty"` - Lifecycle *Lifecycle `json:"lifecycle,omitempty"` + CPU resource.Quantity `json:"cpu,omitempty"` + VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` + LivenessProbe *LivenessProbe `json:"livenessProbe,omitempty"` + Lifecycle *Lifecycle `json:"lifecycle,omitempty"` // Optional: Defaults to /dev/termination-log TerminationMessagePath string `json:"terminationMessagePath,omitempty"` // Optional: Default to false. @@ -747,9 +748,29 @@ type NodeResources struct { Capacity ResourceList `json:"capacity,omitempty"` } +// ResourceName is the name identifying various resources in a ResourceList. type ResourceName string -type ResourceList map[ResourceName]util.IntOrString +const ( + // CPU, in cores. (500m = .5 cores) + ResourceCPU ResourceName = "cpu" + // Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceMemory ResourceName = "memory" +) + +// ResourceList is a set of (resource name, quantity) pairs. +type ResourceList map[ResourceName]resource.Quantity + +// Get is a convenience function, which returns a 0 quantity if the +// resource list is nil, empty, or lacks a value for the requested resource. +// Treat as read only! +func (rl ResourceList) Get(name ResourceName) *resource.Quantity { + if rl == nil { + return &resource.Quantity{} + } + q := rl[name] + return &q +} // Node is a worker node in Kubernetenes // The name of the node according to etcd is in ObjectMeta.Name. diff --git a/pkg/api/v1beta1/conversion.go b/pkg/api/v1beta1/conversion.go index f4ff1f5a854..3248d31d4d8 100644 --- a/pkg/api/v1beta1/conversion.go +++ b/pkg/api/v1beta1/conversion.go @@ -18,10 +18,13 @@ package v1beta1 import ( "errors" + "fmt" "strconv" newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) func init() { @@ -38,7 +41,7 @@ func init() { // newer.Scheme.AddStructFieldConversion(string(""), "Status", string(""), "Condition") // newer.Scheme.AddStructFieldConversion(string(""), "Condition", string(""), "Status") - newer.Scheme.AddConversionFuncs( + err := newer.Scheme.AddConversionFuncs( // TypeMeta must be split into two objects func(in *newer.TypeMeta, out *TypeMeta, s conversion.Scope) error { out.Kind = in.Kind @@ -582,5 +585,61 @@ func init() { out.Timestamp = in.Timestamp return s.Convert(&in.InvolvedObject, &out.InvolvedObject, 0) }, + + // This is triggered for the Memory field of Container. + func(in *int64, out *resource.Quantity, s conversion.Scope) error { + out.Set(*in) + out.Format = resource.BinarySI + return nil + }, + func(in *resource.Quantity, out *int64, s conversion.Scope) error { + *out = in.Value() + return nil + }, + + // This is triggered by the CPU field of Container. + // Note that if we add other int/Quantity conversions my + // simple hack (int64=Value(), int=MilliValue()) here won't work. + func(in *int, out *resource.Quantity, s conversion.Scope) error { + out.SetMilli(int64(*in)) + out.Format = resource.DecimalSI + return nil + }, + func(in *resource.Quantity, out *int, s conversion.Scope) error { + *out = int(in.MilliValue()) + return nil + }, + + // Convert resource lists. + func(in *ResourceList, out *newer.ResourceList, s conversion.Scope) error { + *out = newer.ResourceList{} + for k, v := range *in { + fv, err := strconv.ParseFloat(v.String(), 64) + if err != nil { + return fmt.Errorf("value '%v' of '%v': %v", v, k, err) + } + if k == ResourceCPU { + (*out)[newer.ResourceCPU] = *resource.NewMilliQuantity(int64(fv*1000), resource.DecimalSI) + } else { + (*out)[newer.ResourceName(k)] = *resource.NewQuantity(int64(fv), resource.BinarySI) + } + } + return nil + }, + func(in *newer.ResourceList, out *ResourceList, s conversion.Scope) error { + *out = ResourceList{} + for k, v := range *in { + if k == newer.ResourceCPU { + (*out)[ResourceCPU] = util.NewIntOrStringFromString(fmt.Sprintf("%v", float64(v.MilliValue())/1000)) + } else { + (*out)[ResourceName(k)] = util.NewIntOrStringFromInt(int(v.Value())) + } + } + return nil + }, ) + if err != nil { + // If one of the conversion functions is malformed, detect it immediately. + panic(err) + } } diff --git a/pkg/api/v1beta1/conversion_test.go b/pkg/api/v1beta1/conversion_test.go index 01fc2d170cd..a3a5a8cbed9 100644 --- a/pkg/api/v1beta1/conversion_test.go +++ b/pkg/api/v1beta1/conversion_test.go @@ -201,7 +201,7 @@ func TestMinionListConversionToNew(t *testing.T) { if err != nil { t.Errorf("Unexpected error: %v", err) } - if e, a := item.newML, got; !reflect.DeepEqual(e, a) { + if e, a := item.newML, got; !newer.Semantic.DeepEqual(e, a) { t.Errorf("Expected: %#v, got %#v", e, a) } } @@ -234,7 +234,7 @@ func TestMinionListConversionToOld(t *testing.T) { if err != nil { t.Errorf("Unexpected error: %v", err) } - if e, a := oldML, got; !reflect.DeepEqual(e, a) { + if e, a := oldML, got; !newer.Semantic.DeepEqual(e, a) { t.Errorf("Expected: %#v, got %#v", e, a) } } diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index fa4eebac2f3..77e19b6b9b5 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -254,7 +254,7 @@ type Container struct { Ports []Port `json:"ports,omitempty" description:"list of ports to expose from the container"` Env []EnvVar `json:"env,omitempty" description:"list of environment variables to set in the container"` // Optional: Defaults to unlimited. - Memory int `json:"memory,omitempty" description:"memory limit in bytes; defaults to unlimited"` + Memory int64 `json:"memory,omitempty" description:"memory limit in bytes; defaults to unlimited"` // Optional: Defaults to unlimited. CPU int `json:"cpu,omitempty" description:"CPU share in thousandths of a core"` VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" description:"pod volumes to mount into the container's filesystem"` @@ -583,6 +583,13 @@ type NodeResources struct { type ResourceName string +const ( + // CPU, in cores. (floating point w/ 3 decimal places) + ResourceCPU ResourceName = "cpu" + // Memory, in bytes. + ResourceMemory ResourceName = "memory" +) + type ResourceList map[ResourceName]util.IntOrString // Minion is a worker node in Kubernetenes. diff --git a/pkg/api/v1beta2/conversion.go b/pkg/api/v1beta2/conversion.go index 10e4ad98b0c..00d8cfb313a 100644 --- a/pkg/api/v1beta2/conversion.go +++ b/pkg/api/v1beta2/conversion.go @@ -18,10 +18,13 @@ package v1beta2 import ( "errors" + "fmt" "strconv" newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) func init() { @@ -38,7 +41,7 @@ func init() { // newer.Scheme.AddStructFieldConversion(string(""), "Status", string(""), "Condition") // newer.Scheme.AddStructFieldConversion(string(""), "Condition", string(""), "Status") - newer.Scheme.AddConversionFuncs( + err := newer.Scheme.AddConversionFuncs( // TypeMeta must be split into two objects func(in *newer.TypeMeta, out *TypeMeta, s conversion.Scope) error { out.Kind = in.Kind @@ -498,5 +501,61 @@ func init() { out.Timestamp = in.Timestamp return s.Convert(&in.InvolvedObject, &out.InvolvedObject, 0) }, + + // This is triggered for the Memory field of Container. + func(in *int64, out *resource.Quantity, s conversion.Scope) error { + out.Set(*in) + out.Format = resource.BinarySI + return nil + }, + func(in *resource.Quantity, out *int64, s conversion.Scope) error { + *out = in.Value() + return nil + }, + + // This is triggered by the CPU field of Container. + // Note that if we add other int/Quantity conversions my + // simple hack (int64=Value(), int=MilliValue()) here won't work. + func(in *int, out *resource.Quantity, s conversion.Scope) error { + out.SetMilli(int64(*in)) + out.Format = resource.DecimalSI + return nil + }, + func(in *resource.Quantity, out *int, s conversion.Scope) error { + *out = int(in.MilliValue()) + return nil + }, + + // Convert resource lists. + func(in *ResourceList, out *newer.ResourceList, s conversion.Scope) error { + *out = newer.ResourceList{} + for k, v := range *in { + fv, err := strconv.ParseFloat(v.String(), 64) + if err != nil { + return fmt.Errorf("value '%v' of '%v': %v", v, k, err) + } + if k == ResourceCPU { + (*out)[newer.ResourceCPU] = *resource.NewMilliQuantity(int64(fv*1000), resource.DecimalSI) + } else { + (*out)[newer.ResourceName(k)] = *resource.NewQuantity(int64(fv), resource.BinarySI) + } + } + return nil + }, + func(in *newer.ResourceList, out *ResourceList, s conversion.Scope) error { + *out = ResourceList{} + for k, v := range *in { + if k == newer.ResourceCPU { + (*out)[ResourceCPU] = util.NewIntOrStringFromString(fmt.Sprintf("%v", float64(v.MilliValue())/1000)) + } else { + (*out)[ResourceName(k)] = util.NewIntOrStringFromInt(int(v.Value())) + } + } + return nil + }, ) + if err != nil { + // If one of the conversion functions is malformed, detect it immediately. + panic(err) + } } diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index 7438f264fa8..e8c26dab202 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -218,7 +218,7 @@ type Container struct { Ports []Port `json:"ports,omitempty" description:"list of ports to expose from the container"` Env []EnvVar `json:"env,omitempty" description:"list of environment variables to set in the container"` // Optional: Defaults to unlimited. - Memory int `json:"memory,omitempty" description:"memory limit in bytes; defaults to unlimited"` + Memory int64 `json:"memory,omitempty" description:"memory limit in bytes; defaults to unlimited"` // Optional: Defaults to unlimited. CPU int `json:"cpu,omitempty" description:"CPU share in thousandths of a core"` VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" description:"pod volumes to mount into the container's filesystem"` @@ -546,6 +546,13 @@ type NodeResources struct { type ResourceName string +const ( + // CPU, in cores. (500m = .5 cores) + ResourceCPU ResourceName = "cpu" + // Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceMemory ResourceName = "memory" +) + type ResourceList map[ResourceName]util.IntOrString // Minion is a worker node in Kubernetenes. diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index daf7b01b42d..cca9fed96d3 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -17,6 +17,7 @@ limitations under the License. package v1beta3 import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -326,13 +327,13 @@ type Container struct { WorkingDir string `json:"workingDir,omitempty"` Ports []Port `json:"ports,omitempty"` Env []EnvVar `json:"env,omitempty"` - // Optional: Defaults to unlimited. - Memory int `json:"memory,omitempty"` - // Optional: Defaults to unlimited. - CPU int `json:"cpu,omitempty"` - VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` - LivenessProbe *LivenessProbe `json:"livenessProbe,omitempty"` - Lifecycle *Lifecycle `json:"lifecycle,omitempty"` + // Optional: Defaults to unlimited. Units: bytes. + Memory resource.Quantity `json:"memory,omitempty"` + // Optional: Defaults to unlimited. Units: Cores. (500m == 1/2 core) + CPU resource.Quantity `json:"cpu,omitempty"` + VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` + LivenessProbe *LivenessProbe `json:"livenessProbe,omitempty"` + Lifecycle *Lifecycle `json:"lifecycle,omitempty"` // Optional: Defaults to /dev/termination-log TerminationMessagePath string `json:"terminationMessagePath,omitempty"` // Optional: Default to false. @@ -776,9 +777,18 @@ type NodeCondition struct { Message string `json:"message,omitempty"` } +// ResourceName is the name identifying various resources in a ResourceList. type ResourceName string -type ResourceList map[ResourceName]util.IntOrString +const ( + // CPU, in cores. (500m = .5 cores) + ResourceCPU ResourceName = "cpu" + // Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceMemory ResourceName = "memory" +) + +// ResourceList is a set of (resource name, quantity) pairs. +type ResourceList map[ResourceName]resource.Quantity // Node is a worker node in Kubernetes. // The name of the node according to etcd is in ID. diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 4731c48b886..8566d8012c1 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -18,7 +18,6 @@ package validation import ( "fmt" - "reflect" "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" @@ -429,7 +428,7 @@ func ValidatePodUpdate(newPod, oldPod *api.Pod) errs.ValidationErrorList { newContainers = append(newContainers, container) } pod.Spec.Containers = newContainers - if !reflect.DeepEqual(pod.Spec, oldPod.Spec) { + if !api.Semantic.DeepEqual(pod.Spec, oldPod.Spec) { // TODO: a better error would include all immutable fields explicitly. allErrs = append(allErrs, errs.NewFieldInvalid("spec.containers", newPod.Spec.Containers, "some fields are immutable")) } @@ -586,7 +585,7 @@ func ValidateMinion(minion *api.Node) errs.ValidationErrorList { func ValidateMinionUpdate(oldMinion *api.Node, minion *api.Node) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} - if !reflect.DeepEqual(minion.Status, api.NodeStatus{}) { + if !api.Semantic.DeepEqual(minion.Status, api.NodeStatus{}) { allErrs = append(allErrs, errs.NewFieldInvalid("status", minion.Status, "status must be empty")) } @@ -596,7 +595,7 @@ func ValidateMinionUpdate(oldMinion *api.Node, minion *api.Node) errs.Validation // Clear status oldMinion.Status = minion.Status - if !reflect.DeepEqual(oldMinion, minion) { + if !api.Semantic.DeepEqual(oldMinion, minion) { glog.V(4).Infof("Update failed validation %#v vs %#v", oldMinion, minion) allErrs = append(allErrs, fmt.Errorf("update contains more than labels or capacity changes")) } diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index a93ba27f48a..827aa40ad53 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -22,6 +22,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -372,8 +373,8 @@ func TestValidateManifest(t *testing.T) { Image: "image", Command: []string{"foo", "bar"}, WorkingDir: "/tmp", - Memory: 1, - CPU: 1, + Memory: resource.MustParse("1"), + CPU: resource.MustParse("1"), Ports: []api.Port{ {Name: "p1", ContainerPort: 80, HostPort: 8080}, {Name: "p2", ContainerPort: 81}, @@ -623,7 +624,7 @@ func TestValidatePodUpdate(t *testing.T) { Containers: []api.Container{ { Image: "foo:V1", - CPU: 100, + CPU: resource.MustParse("100m"), }, }, }, @@ -634,7 +635,7 @@ func TestValidatePodUpdate(t *testing.T) { Containers: []api.Container{ { Image: "foo:V2", - CPU: 1000, + CPU: resource.MustParse("1000m"), }, }, }, @@ -1300,8 +1301,8 @@ func TestValidateMinionUpdate(t *testing.T) { }, Spec: api.NodeSpec{ Capacity: api.ResourceList{ - "cpu": util.NewIntOrStringFromInt(10000), - "memory": util.NewIntOrStringFromInt(100), + api.ResourceCPU: resource.MustParse("10000"), + api.ResourceMemory: resource.MustParse("100"), }, }, }, api.Node{ @@ -1310,8 +1311,8 @@ func TestValidateMinionUpdate(t *testing.T) { }, Spec: api.NodeSpec{ Capacity: api.ResourceList{ - "cpu": util.NewIntOrStringFromInt(100), - "memory": util.NewIntOrStringFromInt(10000), + api.ResourceCPU: resource.MustParse("100"), + api.ResourceMemory: resource.MustParse("10000"), }, }, }, true}, @@ -1322,8 +1323,8 @@ func TestValidateMinionUpdate(t *testing.T) { }, Spec: api.NodeSpec{ Capacity: api.ResourceList{ - "cpu": util.NewIntOrStringFromInt(10000), - "memory": util.NewIntOrStringFromInt(100), + api.ResourceCPU: resource.MustParse("10000"), + api.ResourceMemory: resource.MustParse("100"), }, }, }, api.Node{ @@ -1333,8 +1334,8 @@ func TestValidateMinionUpdate(t *testing.T) { }, Spec: api.NodeSpec{ Capacity: api.ResourceList{ - "cpu": util.NewIntOrStringFromInt(100), - "memory": util.NewIntOrStringFromInt(10000), + api.ResourceCPU: resource.MustParse("100"), + api.ResourceMemory: resource.MustParse("10000"), }, }, }, true}, diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 34ba0290e59..7bfff9bfade 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -28,9 +28,9 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" - "github.com/GoogleCloudPlatform/kubernetes/pkg/resources" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/version" @@ -96,7 +96,7 @@ func (c *testClient) Setup() *testClient { func (c *testClient) Validate(t *testing.T, received runtime.Object, err error) { c.ValidateCommon(t, err) - if c.Response.Body != nil && !reflect.DeepEqual(c.Response.Body, received) { + if c.Response.Body != nil && !api.Semantic.DeepEqual(c.Response.Body, received) { t.Errorf("bad response for request %#v: expected %#v, got %#v", c.Request, c.Response.Body, received) } } @@ -734,8 +734,8 @@ func TestCreateMinion(t *testing.T) { }, Spec: api.NodeSpec{ Capacity: api.ResourceList{ - resources.CPU: util.NewIntOrStringFromInt(1000), - resources.Memory: util.NewIntOrStringFromInt(1024 * 1024), + api.ResourceCPU: resource.MustParse("1000m"), + api.ResourceMemory: resource.MustParse("1Mi"), }, }, } diff --git a/pkg/cloudprovider/gce/gce.go b/pkg/cloudprovider/gce/gce.go index 991b49186bd..5916918f9cb 100644 --- a/pkg/cloudprovider/gce/gce.go +++ b/pkg/cloudprovider/gce/gce.go @@ -28,13 +28,13 @@ import ( "strings" "time" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" + "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" + "code.google.com/p/goauth2/compute/serviceaccount" compute "code.google.com/p/google-api-go-client/compute/v1" container "code.google.com/p/google-api-go-client/container/v1beta1" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" - "github.com/GoogleCloudPlatform/kubernetes/pkg/resources" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/golang/glog" ) @@ -338,11 +338,12 @@ func (gce *GCECloud) List(filter string) ([]string, error) { return instances, nil } -func makeResources(cpu float32, memory float32) *api.NodeResources { +// cpu is in cores, memory is in GiB +func makeResources(cpu float64, memory float64) *api.NodeResources { return &api.NodeResources{ Capacity: api.ResourceList{ - resources.CPU: util.NewIntOrStringFromInt(int(cpu * 1000)), - resources.Memory: util.NewIntOrStringFromInt(int(memory * 1024 * 1024 * 1024)), + api.ResourceCPU: *resource.NewMilliQuantity(int64(cpu*1000), resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(int64(memory*1024*1024*1024), resource.BinarySI), }, } } @@ -359,6 +360,7 @@ func (gce *GCECloud) GetNodeResources(name string) (*api.NodeResources, error) { if err != nil { return nil, err } + // TODO: actually read machine size instead of this awful hack. switch canonicalizeMachineType(res.MachineType) { case "f1-micro": return makeResources(1, 0.6), nil diff --git a/pkg/cloudprovider/openstack/openstack.go b/pkg/cloudprovider/openstack/openstack.go index f744b3b100b..4e5947d6cca 100644 --- a/pkg/cloudprovider/openstack/openstack.go +++ b/pkg/cloudprovider/openstack/openstack.go @@ -36,8 +36,8 @@ import ( "github.com/rackspace/gophercloud/pagination" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/golang/glog" ) @@ -169,11 +169,11 @@ func (os *OpenStack) Instances() (cloudprovider.Instances, bool) { for _, flavor := range flavorList { rsrc := api.NodeResources{ Capacity: api.ResourceList{ - "cpu": util.NewIntOrStringFromInt(flavor.VCPUs), - "memory": util.NewIntOrStringFromString(fmt.Sprintf("%dMiB", flavor.RAM)), - "openstack.org/disk": util.NewIntOrStringFromString(fmt.Sprintf("%dGB", flavor.Disk)), - "openstack.org/rxTxFactor": util.NewIntOrStringFromInt(int(flavor.RxTxFactor * 1000)), - "openstack.org/swap": util.NewIntOrStringFromString(fmt.Sprintf("%dMiB", flavor.Swap)), + api.ResourceCPU: *resource.NewMilliQuantity(int64(flavor.VCPUs*1000), resource.DecimalSI), + api.ResourceMemory: resource.MustParse(fmt.Sprintf("%dMi", flavor.RAM)), + "openstack.org/disk": resource.MustParse(fmt.Sprintf("%dG", flavor.Disk)), + "openstack.org/rxTxFactor": *resource.NewQuantity(int64(flavor.RxTxFactor*1000), resource.DecimalSI), + "openstack.org/swap": resource.MustParse(fmt.Sprintf("%dMi", flavor.Swap)), }, } flavor_to_resource[flavor.ID] = &rsrc diff --git a/pkg/controller/replication_controller_test.go b/pkg/controller/replication_controller_test.go index 877882ece57..5673a1653c9 100644 --- a/pkg/controller/replication_controller_test.go +++ b/pkg/controller/replication_controller_test.go @@ -21,7 +21,6 @@ import ( "net/http" "net/http/httptest" "path" - "reflect" "sync" "testing" "time" @@ -233,7 +232,7 @@ func TestCreateReplica(t *testing.T) { if err != nil { t.Errorf("Unexpected error: %#v", err) } - if !reflect.DeepEqual(&expectedPod, actualPod) { + if !api.Semantic.DeepEqual(&expectedPod, actualPod) { t.Logf("Body: %s", fakeHandler.RequestBody) t.Errorf("Unexpected mismatch. Expected\n %#v,\n Got:\n %#v", &expectedPod, actualPod) } @@ -345,7 +344,7 @@ func TestWatchControllers(t *testing.T) { var testControllerSpec api.ReplicationController received := make(chan struct{}) manager.syncHandler = func(controllerSpec api.ReplicationController) error { - if !reflect.DeepEqual(controllerSpec, testControllerSpec) { + if !api.Semantic.DeepEqual(controllerSpec, testControllerSpec) { t.Errorf("Expected %#v, but got %#v", testControllerSpec, controllerSpec) } close(received) diff --git a/pkg/conversion/deep_equal_test.go b/pkg/conversion/deep_equal_test.go index 5d4a5dabbb7..d89ab53e5cf 100644 --- a/pkg/conversion/deep_equal_test.go +++ b/pkg/conversion/deep_equal_test.go @@ -22,15 +22,28 @@ import ( func TestEqualities(t *testing.T) { e := Equalities{} + type Bar struct { + X int + } + type Baz struct { + Y Bar + } err := e.AddFuncs( func(a, b int) bool { return a+1 == b }, + func(a, b Bar) bool { + return a.X*10 == b.X + }, ) if err != nil { t.Fatalf("Unexpected: %v", err) } + type Foo struct { + X int + } + table := []struct { a, b interface{} equal bool @@ -38,6 +51,10 @@ func TestEqualities(t *testing.T) { {1, 2, true}, {2, 1, false}, {"foo", "foo", true}, + {Foo{1}, Foo{2}, true}, + {Bar{1}, Bar{10}, true}, + {&Bar{1}, &Bar{10}, true}, + {Baz{Bar{1}}, Baz{Bar{10}}, true}, {map[string]int{"foo": 1}, map[string]int{"foo": 2}, true}, {map[string]int{}, map[string]int(nil), true}, {[]int{}, []int(nil), true}, diff --git a/pkg/kubelet/config/config_test.go b/pkg/kubelet/config/config_test.go index 245008f52d0..fdf7f9fed9f 100644 --- a/pkg/kubelet/config/config_test.go +++ b/pkg/kubelet/config/config_test.go @@ -17,7 +17,6 @@ limitations under the License. package config import ( - "reflect" "sort" "testing" @@ -65,13 +64,7 @@ func CreateValidPod(name, namespace, source string) api.BoundPod { } func CreatePodUpdate(op kubelet.PodOperation, source string, pods ...api.BoundPod) kubelet.PodUpdate { - // We deliberately return an empty slice instead of a nil pointer here - // because reflect.DeepEqual differentiates between the two and we need to - // pick one for consistency. newPods := make([]api.BoundPod, len(pods)) - if len(pods) == 0 { - return kubelet.PodUpdate{newPods, op, source} - } for i := range pods { newPods[i] = pods[i] } @@ -89,7 +82,7 @@ func expectPodUpdate(t *testing.T, ch <-chan kubelet.PodUpdate, expected ...kube for i := range expected { update := <-ch sort.Sort(sortedPods(update.Pods)) - if !reflect.DeepEqual(expected[i], update) { + if !api.Semantic.DeepEqual(expected[i], update) { t.Fatalf("Expected %#v, Got %#v", expected[i], update) } } diff --git a/pkg/kubelet/config/file_test.go b/pkg/kubelet/config/file_test.go index 135bdea5467..3c9e2a3e4ee 100644 --- a/pkg/kubelet/config/file_test.go +++ b/pkg/kubelet/config/file_test.go @@ -20,7 +20,6 @@ import ( "encoding/json" "io/ioutil" "os" - "reflect" "sort" "testing" "time" @@ -130,7 +129,7 @@ func TestReadFromFile(t *testing.T) { Containers: []api.Container{{Image: "test/image", TerminationMessagePath: "/dev/termination-log"}}, }, }) - if !reflect.DeepEqual(expected, update) { + if !api.Semantic.DeepEqual(expected, update) { t.Fatalf("Expected %#v, Got %#v", expected, update) } @@ -171,7 +170,7 @@ func TestExtractFromValidDataFile(t *testing.T) { } update := (<-ch).(kubelet.PodUpdate) expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource, expectedPod) - if !reflect.DeepEqual(expected, update) { + if !api.Semantic.DeepEqual(expected, update) { t.Errorf("Expected %#v, Got %#v", expected, update) } } @@ -192,7 +191,7 @@ func TestExtractFromEmptyDir(t *testing.T) { update := (<-ch).(kubelet.PodUpdate) expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource) - if !reflect.DeepEqual(expected, update) { + if !api.Semantic.DeepEqual(expected, update) { t.Errorf("Expected %#v, Got %#v", expected, update) } } @@ -242,7 +241,7 @@ func TestExtractFromDir(t *testing.T) { expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource, pods...) sort.Sort(sortedPods(update.Pods)) sort.Sort(sortedPods(expected.Pods)) - if !reflect.DeepEqual(expected, update) { + if !api.Semantic.DeepEqual(expected, update) { t.Fatalf("Expected %#v, Got %#v", expected, update) } for i := range update.Pods { diff --git a/pkg/kubelet/config/http_test.go b/pkg/kubelet/config/http_test.go index a1c0b7d101b..016b70373fc 100644 --- a/pkg/kubelet/config/http_test.go +++ b/pkg/kubelet/config/http_test.go @@ -19,7 +19,6 @@ package config import ( "encoding/json" "net/http/httptest" - "reflect" "testing" "time" @@ -193,7 +192,7 @@ func TestExtractFromHTTP(t *testing.T) { continue } update := (<-ch).(kubelet.PodUpdate) - if !reflect.DeepEqual(testCase.expected, update) { + if !api.Semantic.DeepEqual(testCase.expected, update) { t.Errorf("%s: Expected: %#v, Got: %#v", testCase.desc, testCase.expected, update) } for i := range update.Pods { diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index d84d3df14a2..17ddca70e09 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -398,7 +398,7 @@ func makePortsAndBindings(container *api.Container) (map[docker.Port]struct{}, m return exposedPorts, portBindings } -func milliCPUToShares(milliCPU int) int { +func milliCPUToShares(milliCPU int64) int64 { if milliCPU == 0 { // zero milliCPU means unset. Use kernel default. return 0 @@ -537,8 +537,8 @@ func (kl *Kubelet) runContainer(pod *api.BoundPod, container *api.Container, pod ExposedPorts: exposedPorts, Hostname: pod.Name, Image: container.Image, - Memory: int64(container.Memory), - CPUShares: int64(milliCPUToShares(container.CPU)), + Memory: container.Memory.Value(), + CPUShares: milliCPUToShares(container.CPU.MilliValue()), WorkingDir: container.WorkingDir, }, } diff --git a/pkg/registry/controller/rest_test.go b/pkg/registry/controller/rest_test.go index febe607f25d..585a956c0ce 100644 --- a/pkg/registry/controller/rest_test.go +++ b/pkg/registry/controller/rest_test.go @@ -20,7 +20,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "reflect" "strings" "testing" "time" @@ -136,7 +135,7 @@ func TestControllerDecode(t *testing.T) { t.Errorf("unexpected error: %v", err) } - if !reflect.DeepEqual(controller, controllerOut) { + if !api.Semantic.DeepEqual(controller, controllerOut) { t.Errorf("Expected %#v, found %#v", controller, controllerOut) } } @@ -208,7 +207,7 @@ func TestControllerParsing(t *testing.T) { t.Errorf("unexpected error: %v", err) } - if !reflect.DeepEqual(controller, expectedController) { + if !api.Semantic.DeepEqual(controller, expectedController) { t.Errorf("Parsing failed: %s %#v %#v", string(data), controller, expectedController) } } @@ -375,7 +374,7 @@ func TestFillCurrentState(t *testing.T) { if controller.Status.Replicas != 2 { t.Errorf("expected 2, got: %d", controller.Status.Replicas) } - if !reflect.DeepEqual(fakeLister.s, labels.Set(controller.Spec.Selector).AsSelector()) { + if !api.Semantic.DeepEqual(fakeLister.s, labels.Set(controller.Spec.Selector).AsSelector()) { t.Errorf("unexpected output: %#v %#v", labels.Set(controller.Spec.Selector).AsSelector(), fakeLister.s) } } diff --git a/pkg/registry/etcd/etcd_test.go b/pkg/registry/etcd/etcd_test.go index 6326b683df3..ef6647eb6b1 100644 --- a/pkg/registry/etcd/etcd_test.go +++ b/pkg/registry/etcd/etcd_test.go @@ -448,7 +448,7 @@ func TestEtcdUpdatePodNotScheduled(t *testing.T) { } var podOut api.Pod latest.Codec.DecodeInto([]byte(response.Node.Value), &podOut) - if !reflect.DeepEqual(podOut, podIn) { + if !api.Semantic.DeepEqual(podOut, podIn) { t.Errorf("expected: %v, got: %v", podOut, podIn) } } @@ -529,7 +529,7 @@ func TestEtcdUpdatePodScheduled(t *testing.T) { } var podOut api.Pod latest.Codec.DecodeInto([]byte(response.Node.Value), &podOut) - if !reflect.DeepEqual(podOut, podIn) { + if !api.Semantic.DeepEqual(podOut, podIn) { t.Errorf("expected: %#v, got: %#v", podOut, podIn) } @@ -542,7 +542,7 @@ func TestEtcdUpdatePodScheduled(t *testing.T) { t.Fatalf("unexpected error decoding response: %v", err) } - if len(list.Items) != 2 || !reflect.DeepEqual(list.Items[0].Spec, podIn.Spec) { + if len(list.Items) != 2 || !api.Semantic.DeepEqual(list.Items[0].Spec, podIn.Spec) { t.Errorf("unexpected container list: %d\n items[0] - %#v\n podin.spec - %#v\n", len(list.Items), list.Items[0].Spec, podIn.Spec) } } diff --git a/pkg/resources/doc.go b/pkg/resources/doc.go deleted file mode 100644 index 1c42e5b38be..00000000000 --- a/pkg/resources/doc.go +++ /dev/null @@ -1,18 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// package resources has constants and utilities for dealing with resources -package resources diff --git a/pkg/resources/resources.go b/pkg/resources/resources.go deleted file mode 100644 index 4647b367b59..00000000000 --- a/pkg/resources/resources.go +++ /dev/null @@ -1,75 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package resources - -import ( - "strconv" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" - "github.com/golang/glog" -) - -const ( - CPU api.ResourceName = "cpu" - Memory api.ResourceName = "memory" -) - -// TODO: None of these currently handle SI units - -func GetFloatResource(resources api.ResourceList, name api.ResourceName, def float64) float64 { - value, found := resources[name] - if !found { - return def - } - if value.Kind == util.IntstrInt { - return float64(value.IntVal) - } - result, err := strconv.ParseFloat(value.StrVal, 64) - if err != nil { - glog.Errorf("parsing failed for %s: %s", name, value.StrVal) - return def - } - return result -} - -func GetIntegerResource(resources api.ResourceList, name api.ResourceName, def int) int { - value, found := resources[name] - if !found { - return def - } - if value.Kind == util.IntstrInt { - return value.IntVal - } - result, err := strconv.Atoi(value.StrVal) - if err != nil { - glog.Errorf("parsing failed for %s: %s", name, value.StrVal) - return def - } - return result -} - -func GetStringResource(resources api.ResourceList, name api.ResourceName, def string) string { - value, found := resources[name] - if !found { - return def - } - if value.Kind == util.IntstrInt { - return strconv.Itoa(value.IntVal) - } - return value.StrVal -} diff --git a/pkg/resources/resources_test.go b/pkg/resources/resources_test.go deleted file mode 100644 index 263e25a54d2..00000000000 --- a/pkg/resources/resources_test.go +++ /dev/null @@ -1,169 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package resources - -import ( - "testing" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" -) - -func TestGetInteger(t *testing.T) { - tests := []struct { - res api.ResourceList - name api.ResourceName - expected int - def int - test string - }{ - { - res: api.ResourceList{}, - name: CPU, - expected: 1, - def: 1, - test: "nothing present", - }, - { - res: api.ResourceList{ - CPU: util.NewIntOrStringFromInt(2), - }, - name: CPU, - expected: 2, - def: 1, - test: "present", - }, - { - res: api.ResourceList{ - Memory: util.NewIntOrStringFromInt(2), - }, - name: CPU, - expected: 1, - def: 1, - test: "not-present", - }, - { - res: api.ResourceList{ - CPU: util.NewIntOrStringFromString("2"), - }, - name: CPU, - expected: 2, - def: 1, - test: "present-string", - }, - { - res: api.ResourceList{ - CPU: util.NewIntOrStringFromString("foo"), - }, - name: CPU, - expected: 1, - def: 1, - test: "present-invalid", - }, - } - - for _, test := range tests { - val := GetIntegerResource(test.res, test.name, test.def) - if val != test.expected { - t.Errorf("expected: %d found %d", test.expected, val) - } - } -} -func TestGetFloat(t *testing.T) { - tests := []struct { - res api.ResourceList - name api.ResourceName - expected float64 - def float64 - test string - }{ - { - res: api.ResourceList{}, - name: CPU, - expected: 1.5, - def: 1.5, - test: "nothing present", - }, - { - res: api.ResourceList{ - CPU: util.NewIntOrStringFromInt(2), - }, - name: CPU, - expected: 2.0, - def: 1.5, - test: "present", - }, - { - res: api.ResourceList{ - CPU: util.NewIntOrStringFromString("2.5"), - }, - name: CPU, - expected: 2.5, - def: 1, - test: "present-string", - }, - { - res: api.ResourceList{ - CPU: util.NewIntOrStringFromString("foo"), - }, - name: CPU, - expected: 1, - def: 1, - test: "present-invalid", - }, - } - - for _, test := range tests { - val := GetFloatResource(test.res, test.name, test.def) - if val != test.expected { - t.Errorf("expected: %f found %f", test.expected, val) - } - } -} -func TestGetString(t *testing.T) { - tests := []struct { - res api.ResourceList - name api.ResourceName - expected string - def string - test string - }{ - { - res: api.ResourceList{}, - name: CPU, - expected: "foo", - def: "foo", - test: "nothing present", - }, - { - res: api.ResourceList{ - CPU: util.NewIntOrStringFromString("bar"), - }, - name: CPU, - expected: "bar", - def: "foo", - test: "present", - }, - } - - for _, test := range tests { - val := GetStringResource(test.res, test.name, test.def) - if val != test.expected { - t.Errorf("expected: %s found %s", test.expected, val) - } - } -} diff --git a/pkg/scheduler/predicates.go b/pkg/scheduler/predicates.go index 6768016b832..9e4aa2ec738 100644 --- a/pkg/scheduler/predicates.go +++ b/pkg/scheduler/predicates.go @@ -22,7 +22,6 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" - "github.com/GoogleCloudPlatform/kubernetes/pkg/resources" "github.com/golang/glog" ) @@ -89,15 +88,15 @@ type ResourceFit struct { } type resourceRequest struct { - milliCPU int - memory int + milliCPU int64 + memory int64 } func getResourceRequest(pod *api.Pod) resourceRequest { result := resourceRequest{} for ix := range pod.Spec.Containers { - result.memory += pod.Spec.Containers[ix].Memory - result.milliCPU += pod.Spec.Containers[ix].CPU + result.memory += pod.Spec.Containers[ix].Memory.Value() + result.milliCPU += pod.Spec.Containers[ix].CPU.MilliValue() } return result } @@ -113,17 +112,16 @@ func (r *ResourceFit) PodFitsResources(pod api.Pod, existingPods []api.Pod, node if err != nil { return false, err } - milliCPURequested := 0 - memoryRequested := 0 + milliCPURequested := int64(0) + memoryRequested := int64(0) for ix := range existingPods { existingRequest := getResourceRequest(&existingPods[ix]) milliCPURequested += existingRequest.milliCPU memoryRequested += existingRequest.memory } - // TODO: convert to general purpose resource matching, when pods ask for resources - totalMilliCPU := int(resources.GetFloatResource(info.Spec.Capacity, resources.CPU, 0) * 1000) - totalMemory := resources.GetIntegerResource(info.Spec.Capacity, resources.Memory, 0) + totalMilliCPU := info.Spec.Capacity.Get(api.ResourceCPU).MilliValue() + totalMemory := info.Spec.Capacity.Get(api.ResourceMemory).Value() fitsCPU := totalMilliCPU == 0 || (totalMilliCPU-milliCPURequested) >= podRequest.milliCPU fitsMemory := totalMemory == 0 || (totalMemory-memoryRequested) >= podRequest.memory diff --git a/pkg/scheduler/predicates_test.go b/pkg/scheduler/predicates_test.go index 106efbcb426..5fe556207f1 100644 --- a/pkg/scheduler/predicates_test.go +++ b/pkg/scheduler/predicates_test.go @@ -21,8 +21,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/resources" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" ) type FakeNodeInfo api.Node @@ -32,17 +31,11 @@ func (n FakeNodeInfo) GetNodeInfo(nodeName string) (*api.Node, error) { return &node, nil } -func makeResources(milliCPU int, memory int) api.NodeResources { +func makeResources(milliCPU int64, memory int64) api.NodeResources { return api.NodeResources{ Capacity: api.ResourceList{ - resources.CPU: util.IntOrString{ - IntVal: milliCPU, - Kind: util.IntstrInt, - }, - resources.Memory: util.IntOrString{ - IntVal: memory, - Kind: util.IntstrInt, - }, + api.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI), }, } } @@ -51,8 +44,8 @@ func newResourcePod(usage ...resourceRequest) api.Pod { containers := []api.Container{} for _, req := range usage { containers = append(containers, api.Container{ - Memory: req.memory, - CPU: req.milliCPU, + Memory: *resource.NewQuantity(req.memory, resource.BinarySI), + CPU: *resource.NewMilliQuantity(req.milliCPU, resource.DecimalSI), }) } return api.Pod{ diff --git a/pkg/scheduler/priorities.go b/pkg/scheduler/priorities.go index c2ec09bbdec..dc3250cb8a2 100644 --- a/pkg/scheduler/priorities.go +++ b/pkg/scheduler/priorities.go @@ -18,13 +18,12 @@ package scheduler import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/resources" "github.com/golang/glog" ) // the unused capacity is calculated on a scale of 0-10 // 0 being the lowest priority and 10 being the highest -func calculateScore(requested, capacity int, node string) int { +func calculateScore(requested, capacity int64, node string) int { if capacity == 0 { return 0 } @@ -32,30 +31,39 @@ func calculateScore(requested, capacity int, node string) int { glog.Errorf("Combined requested resources from existing pods exceeds capacity on minion: %s", node) return 0 } - return ((capacity - requested) * 10) / capacity + return int(((capacity - requested) * 10) / capacity) } // Calculate the occupancy on a node. 'node' has information about the resources on the node. // 'pods' is a list of pods currently scheduled on the node. func calculateOccupancy(pod api.Pod, node api.Node, pods []api.Pod) HostPriority { - totalCPU := 0 - totalMemory := 0 + totalMilliCPU := int64(0) + totalMemory := int64(0) for _, existingPod := range pods { for _, container := range existingPod.Spec.Containers { - totalCPU += container.CPU - totalMemory += container.Memory + totalMilliCPU += container.CPU.MilliValue() + totalMemory += container.Memory.Value() } } // Add the resources requested by the current pod being scheduled. // This also helps differentiate between differently sized, but empty, minions. for _, container := range pod.Spec.Containers { - totalCPU += container.CPU - totalMemory += container.Memory + totalMilliCPU += container.CPU.MilliValue() + totalMemory += container.Memory.Value() } - cpuScore := calculateScore(totalCPU, resources.GetIntegerResource(node.Spec.Capacity, resources.CPU, 0), node.Name) - memoryScore := calculateScore(totalMemory, resources.GetIntegerResource(node.Spec.Capacity, resources.Memory, 0), node.Name) - glog.V(4).Infof("Least Requested Priority, AbsoluteRequested: (%d, %d) Score:(%d, %d)", totalCPU, totalMemory, cpuScore, memoryScore) + capacityMilliCPU := node.Spec.Capacity.Get(api.ResourceCPU).MilliValue() + capacityMemory := node.Spec.Capacity.Get(api.ResourceMemory).Value() + + cpuScore := calculateScore(totalMilliCPU, capacityMilliCPU, node.Name) + memoryScore := calculateScore(totalMemory, capacityMemory, node.Name) + glog.V(4).Infof( + "%v -> %v: Least Requested Priority, AbsoluteRequested: (%d, %d) / (%d, %d) Score: (%d, %d)", + pod.Name, node.Name, + totalMilliCPU, totalMemory, + capacityMilliCPU, capacityMemory, + cpuScore, memoryScore, + ) return HostPriority{ host: node.Name, diff --git a/pkg/scheduler/priorities_test.go b/pkg/scheduler/priorities_test.go index 327d877f44d..23f932e0a0d 100644 --- a/pkg/scheduler/priorities_test.go +++ b/pkg/scheduler/priorities_test.go @@ -21,17 +21,16 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/resources" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" ) -func makeMinion(node string, cpu, memory int) api.Node { +func makeMinion(node string, milliCPU, memory int64) api.Node { return api.Node{ ObjectMeta: api.ObjectMeta{Name: node}, Spec: api.NodeSpec{ Capacity: api.ResourceList{ - resources.CPU: util.NewIntOrStringFromInt(cpu), - resources.Memory: util.NewIntOrStringFromInt(memory), + api.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI), }, }, } @@ -57,14 +56,14 @@ func TestLeastRequested(t *testing.T) { } cpuOnly := api.PodSpec{ Containers: []api.Container{ - {CPU: 1000}, - {CPU: 2000}, + {CPU: resource.MustParse("1000m")}, + {CPU: resource.MustParse("2000m")}, }, } cpuAndMemory := api.PodSpec{ Containers: []api.Container{ - {CPU: 1000, Memory: 2000}, - {CPU: 2000, Memory: 3000}, + {CPU: resource.MustParse("1000m"), Memory: resource.MustParse("2000")}, + {CPU: resource.MustParse("2000m"), Memory: resource.MustParse("3000")}, }, } tests := []struct { diff --git a/pkg/standalone/standalone.go b/pkg/standalone/standalone.go index 7a8408014ab..9042afb7fe0 100644 --- a/pkg/standalone/standalone.go +++ b/pkg/standalone/standalone.go @@ -18,12 +18,12 @@ package standalone import ( "fmt" - "math" "net" "net/http" "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth" @@ -33,7 +33,6 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/config" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" "github.com/GoogleCloudPlatform/kubernetes/pkg/master" - "github.com/GoogleCloudPlatform/kubernetes/pkg/resources" "github.com/GoogleCloudPlatform/kubernetes/pkg/service" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -125,22 +124,10 @@ func RunScheduler(cl *client.Client) { // RunControllerManager starts a controller func RunControllerManager(machineList []string, cl *client.Client, nodeMilliCPU, nodeMemory int64) { - if int64(int(nodeMilliCPU)) != nodeMilliCPU { - glog.Warningf("node_milli_cpu is too big for platform. Clamping: %d -> %d", - nodeMilliCPU, math.MaxInt32) - nodeMilliCPU = math.MaxInt32 - } - - if int64(int(nodeMemory)) != nodeMemory { - glog.Warningf("node_memory is too big for platform. Clamping: %d -> %d", - nodeMemory, math.MaxInt32) - nodeMemory = math.MaxInt32 - } - nodeResources := &api.NodeResources{ Capacity: api.ResourceList{ - resources.CPU: util.NewIntOrStringFromInt(int(nodeMilliCPU)), - resources.Memory: util.NewIntOrStringFromInt(int(nodeMemory)), + api.ResourceCPU: *resource.NewMilliQuantity(nodeMilliCPU, resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(nodeMemory, resource.BinarySI), }, } minionController := minionControllerPkg.NewMinionController(nil, "", machineList, nodeResources, cl) diff --git a/pkg/util/util.go b/pkg/util/util.go index f7ee32f44d7..a84356888ad 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -113,6 +113,14 @@ func (intstr *IntOrString) UnmarshalJSON(value []byte) error { return json.Unmarshal(value, &intstr.IntVal) } +// String returns the string value, or Itoa's the int value. +func (intstr *IntOrString) String() string { + if intstr.Kind == IntstrString { + return intstr.StrVal + } + return strconv.Itoa(intstr.IntVal) +} + // MarshalJSON implements the json.Marshaller interface. func (intstr IntOrString) MarshalJSON() ([]byte, error) { switch intstr.Kind {