Merge pull request #3195 from lavalamp/numericWire

Add numeric type into api
This commit is contained in:
Tim Hockin 2015-01-07 16:15:52 -08:00
commit 5f2dae4dd8
35 changed files with 361 additions and 440 deletions

View File

@ -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)

View File

@ -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

View File

@ -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))
}
}

View File

@ -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
}

View File

@ -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:

View File

@ -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)
}
}

View File

@ -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.

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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.

View File

@ -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)
}
}

View File

@ -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.

View File

@ -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.

View File

@ -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"))
}

View File

@ -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},

View File

@ -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"),
},
},
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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},

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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,
},
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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{

View File

@ -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,

View File

@ -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 {

View File

@ -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)

View File

@ -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 {