Commit 5f2dae4d authored by Tim Hockin's avatar Tim Hockin

Merge pull request #3195 from lavalamp/numericWire

Add numeric type into api
parents 2c54dd5c 2050131b
...@@ -22,20 +22,19 @@ package main ...@@ -22,20 +22,19 @@ package main
import ( import (
"flag" "flag"
"math"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
minionControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/controller" minionControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/controller"
replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller" replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resources"
"github.com/GoogleCloudPlatform/kubernetes/pkg/service" "github.com/GoogleCloudPlatform/kubernetes/pkg/service"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag" "github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag"
...@@ -51,8 +50,9 @@ var ( ...@@ -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.") minionRegexp = flag.String("minion_regexp", "", "If non empty, and -cloud_provider is specified, a regular expression for matching minion VMs.")
machineList util.StringList machineList util.StringList
// TODO: Discover these by pinging the host machines, and rip out these flags. // 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") 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() { func init() {
...@@ -90,18 +90,6 @@ func main() { ...@@ -90,18 +90,6 @@ func main() {
glog.Fatalf("Invalid API configuration: %v", err) 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) go http.ListenAndServe(net.JoinHostPort(address.String(), strconv.Itoa(*port)), nil)
endpoints := service.NewEndpointController(kubeClient) endpoints := service.NewEndpointController(kubeClient)
...@@ -113,8 +101,8 @@ func main() { ...@@ -113,8 +101,8 @@ func main() {
cloud := cloudprovider.InitCloudProvider(*cloudProvider, *cloudConfigFile) cloud := cloudprovider.InitCloudProvider(*cloudProvider, *cloudConfigFile)
nodeResources := &api.NodeResources{ nodeResources := &api.NodeResources{
Capacity: api.ResourceList{ Capacity: api.ResourceList{
resources.CPU: util.NewIntOrStringFromInt(int(*nodeMilliCPU)), api.ResourceCPU: *resource.NewMilliQuantity(*nodeMilliCPU, resource.DecimalSI),
resources.Memory: util.NewIntOrStringFromInt(int(*nodeMemory)), api.ResourceMemory: *nodeMemory,
}, },
} }
minionController := minionControllerPkg.NewMinionController(cloud, *minionRegexp, machineList, nodeResources, kubeClient) minionController := minionControllerPkg.NewMinionController(cloud, *minionRegexp, machineList, nodeResources, kubeClient)
......
...@@ -18,6 +18,28 @@ package api ...@@ -18,6 +18,28 @@ package api
import ( import (
"strings" "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 // TODO: Address these per #1502
......
...@@ -18,7 +18,6 @@ package latest ...@@ -18,7 +18,6 @@ package latest
import ( import (
"encoding/json" "encoding/json"
"reflect"
"strconv" "strconv"
"testing" "testing"
...@@ -167,7 +166,7 @@ func TestInternalRoundTrip(t *testing.T) { ...@@ -167,7 +166,7 @@ func TestInternalRoundTrip(t *testing.T) {
continue continue
} }
if !reflect.DeepEqual(obj, actual) { if !internal.Semantic.DeepEqual(obj, actual) {
t.Errorf("%s: diff %s", k, util.ObjectDiff(obj, actual)) t.Errorf("%s: diff %s", k, util.ObjectDiff(obj, actual))
} }
} }
......
...@@ -102,14 +102,14 @@ const ( ...@@ -102,14 +102,14 @@ const (
DecimalSI = Format("DecimalSI") // e.g., 12M (12 * 10^6) 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. // or others cases where you know the string is valid.
func ParseOrDie(str string) *Quantity { func MustParse(str string) Quantity {
q, err := ParseQuantity(str) q, err := ParseQuantity(str)
if err != nil { if err != nil {
panic(fmt.Errorf("cannot parse '%v': %v", str, err)) panic(fmt.Errorf("cannot parse '%v': %v", str, err))
} }
return q return *q
} }
const ( const (
...@@ -148,7 +148,7 @@ var ( ...@@ -148,7 +148,7 @@ var (
// The maximum value we can represent milli-units for. // The maximum value we can represent milli-units for.
// Compare with the return value of Quantity.Value() to // Compare with the return value of Quantity.Value() to
// see if it's safe to use Quantity.MilliValue(). // 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. // ParseQuantity turns str into a Quantity, or returns an error.
...@@ -403,10 +403,7 @@ func (qf qFlag) String() string { ...@@ -403,10 +403,7 @@ func (qf qFlag) String() string {
// QuantityFlag is a helper that makes a quantity flag (using standard flag package). // QuantityFlag is a helper that makes a quantity flag (using standard flag package).
// Will panic if defaultValue is not a valid quantity. // Will panic if defaultValue is not a valid quantity.
func QuantityFlag(flagName, defaultValue, description string) *Quantity { func QuantityFlag(flagName, defaultValue, description string) *Quantity {
q, err := ParseQuantity(defaultValue) q := MustParse(defaultValue)
if err != nil { flag.Var(qFlag{&q}, flagName, description)
panic(fmt.Errorf("can't use %v as a quantity: %v", defaultValue, err)) return &q
}
flag.Var(qFlag{q}, flagName, description)
return q
} }
...@@ -38,17 +38,17 @@ func ExampleFormat() { ...@@ -38,17 +38,17 @@ func ExampleFormat() {
// cores = 5300m // cores = 5300m
} }
func ExampleParseOrDie() { func ExampleMustParse() {
memorySize := resource.ParseOrDie("5Gi") memorySize := resource.MustParse("5Gi")
fmt.Printf("memorySize = %v (%v)\n", memorySize.Value(), memorySize.Format) 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) 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) 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) fmt.Printf("milliCores = %v (%v)\n", cores2.MilliValue(), cores2.Format)
// Output: // Output:
......
...@@ -27,12 +27,15 @@ import ( ...@@ -27,12 +27,15 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "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/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
docker "github.com/fsouza/go-dockerclient" docker "github.com/fsouza/go-dockerclient"
fuzz "github.com/google/gofuzz" 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.") 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( ...@@ -136,6 +139,16 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
c.RandString(): c.RandString(), 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) { 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) { ...@@ -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) t.Errorf("0: %v: %v\nCodec: %v\nData: %s\nSource: %#v", name, err, codec, string(data), source)
return 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) t.Errorf("1: %v: diff: %v\nCodec: %v\nData: %s\nSource: %#v", name, util.ObjectGoPrintDiff(source, obj2), codec, string(data), source)
return return
} }
...@@ -170,7 +183,7 @@ func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) { ...@@ -170,7 +183,7 @@ func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) {
t.Errorf("2: %v: %v", name, err) t.Errorf("2: %v: %v", name, err)
return 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) t.Errorf("3: %v: diff: %v\nCodec: %v", name, util.ObjectDiff(source, obj3), codec)
return return
} }
...@@ -244,7 +257,7 @@ func TestEncode_Ptr(t *testing.T) { ...@@ -244,7 +257,7 @@ func TestEncode_Ptr(t *testing.T) {
if _, ok := obj2.(*api.Pod); !ok { if _, ok := obj2.(*api.Pod); !ok {
t.Fatalf("Got wrong type") 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) t.Errorf("Expected:\n %#v,\n Got:\n %#v", &pod, obj2)
} }
} }
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package api package api
import ( import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
...@@ -309,12 +310,12 @@ type Container struct { ...@@ -309,12 +310,12 @@ type Container struct {
Ports []Port `json:"ports,omitempty"` Ports []Port `json:"ports,omitempty"`
Env []EnvVar `json:"env,omitempty"` Env []EnvVar `json:"env,omitempty"`
// Optional: Defaults to unlimited. // Optional: Defaults to unlimited.
Memory int `json:"memory,omitempty"` Memory resource.Quantity `json:"memory,omitempty"`
// Optional: Defaults to unlimited. // Optional: Defaults to unlimited.
CPU int `json:"cpu,omitempty"` CPU resource.Quantity `json:"cpu,omitempty"`
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"`
LivenessProbe *LivenessProbe `json:"livenessProbe,omitempty"` LivenessProbe *LivenessProbe `json:"livenessProbe,omitempty"`
Lifecycle *Lifecycle `json:"lifecycle,omitempty"` Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
// Optional: Defaults to /dev/termination-log // Optional: Defaults to /dev/termination-log
TerminationMessagePath string `json:"terminationMessagePath,omitempty"` TerminationMessagePath string `json:"terminationMessagePath,omitempty"`
// Optional: Default to false. // Optional: Default to false.
...@@ -747,9 +748,29 @@ type NodeResources struct { ...@@ -747,9 +748,29 @@ type NodeResources struct {
Capacity ResourceList `json:"capacity,omitempty"` Capacity ResourceList `json:"capacity,omitempty"`
} }
// ResourceName is the name identifying various resources in a ResourceList.
type ResourceName string 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 // Node is a worker node in Kubernetenes
// The name of the node according to etcd is in ObjectMeta.Name. // The name of the node according to etcd is in ObjectMeta.Name.
......
...@@ -18,10 +18,13 @@ package v1beta1 ...@@ -18,10 +18,13 @@ package v1beta1
import ( import (
"errors" "errors"
"fmt"
"strconv" "strconv"
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api" 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/conversion"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
func init() { func init() {
...@@ -38,7 +41,7 @@ func init() { ...@@ -38,7 +41,7 @@ func init() {
// newer.Scheme.AddStructFieldConversion(string(""), "Status", string(""), "Condition") // newer.Scheme.AddStructFieldConversion(string(""), "Status", string(""), "Condition")
// newer.Scheme.AddStructFieldConversion(string(""), "Condition", string(""), "Status") // newer.Scheme.AddStructFieldConversion(string(""), "Condition", string(""), "Status")
newer.Scheme.AddConversionFuncs( err := newer.Scheme.AddConversionFuncs(
// TypeMeta must be split into two objects // TypeMeta must be split into two objects
func(in *newer.TypeMeta, out *TypeMeta, s conversion.Scope) error { func(in *newer.TypeMeta, out *TypeMeta, s conversion.Scope) error {
out.Kind = in.Kind out.Kind = in.Kind
...@@ -582,5 +585,61 @@ func init() { ...@@ -582,5 +585,61 @@ func init() {
out.Timestamp = in.Timestamp out.Timestamp = in.Timestamp
return s.Convert(&in.InvolvedObject, &out.InvolvedObject, 0) 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)
}
} }
...@@ -201,7 +201,7 @@ func TestMinionListConversionToNew(t *testing.T) { ...@@ -201,7 +201,7 @@ func TestMinionListConversionToNew(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) 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) t.Errorf("Expected: %#v, got %#v", e, a)
} }
} }
...@@ -234,7 +234,7 @@ func TestMinionListConversionToOld(t *testing.T) { ...@@ -234,7 +234,7 @@ func TestMinionListConversionToOld(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) 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) t.Errorf("Expected: %#v, got %#v", e, a)
} }
} }
......
...@@ -254,7 +254,7 @@ type Container struct { ...@@ -254,7 +254,7 @@ type Container struct {
Ports []Port `json:"ports,omitempty" description:"list of ports to expose from the container"` 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"` Env []EnvVar `json:"env,omitempty" description:"list of environment variables to set in the container"`
// Optional: Defaults to unlimited. // 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. // Optional: Defaults to unlimited.
CPU int `json:"cpu,omitempty" description:"CPU share in thousandths of a core"` 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"` VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" description:"pod volumes to mount into the container's filesystem"`
...@@ -583,6 +583,13 @@ type NodeResources struct { ...@@ -583,6 +583,13 @@ type NodeResources struct {
type ResourceName string 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 type ResourceList map[ResourceName]util.IntOrString
// Minion is a worker node in Kubernetenes. // Minion is a worker node in Kubernetenes.
......
...@@ -18,10 +18,13 @@ package v1beta2 ...@@ -18,10 +18,13 @@ package v1beta2
import ( import (
"errors" "errors"
"fmt"
"strconv" "strconv"
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api" 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/conversion"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
func init() { func init() {
...@@ -38,7 +41,7 @@ func init() { ...@@ -38,7 +41,7 @@ func init() {
// newer.Scheme.AddStructFieldConversion(string(""), "Status", string(""), "Condition") // newer.Scheme.AddStructFieldConversion(string(""), "Status", string(""), "Condition")
// newer.Scheme.AddStructFieldConversion(string(""), "Condition", string(""), "Status") // newer.Scheme.AddStructFieldConversion(string(""), "Condition", string(""), "Status")
newer.Scheme.AddConversionFuncs( err := newer.Scheme.AddConversionFuncs(
// TypeMeta must be split into two objects // TypeMeta must be split into two objects
func(in *newer.TypeMeta, out *TypeMeta, s conversion.Scope) error { func(in *newer.TypeMeta, out *TypeMeta, s conversion.Scope) error {
out.Kind = in.Kind out.Kind = in.Kind
...@@ -498,5 +501,61 @@ func init() { ...@@ -498,5 +501,61 @@ func init() {
out.Timestamp = in.Timestamp out.Timestamp = in.Timestamp
return s.Convert(&in.InvolvedObject, &out.InvolvedObject, 0) 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)
}
} }
...@@ -218,7 +218,7 @@ type Container struct { ...@@ -218,7 +218,7 @@ type Container struct {
Ports []Port `json:"ports,omitempty" description:"list of ports to expose from the container"` 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"` Env []EnvVar `json:"env,omitempty" description:"list of environment variables to set in the container"`
// Optional: Defaults to unlimited. // 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. // Optional: Defaults to unlimited.
CPU int `json:"cpu,omitempty" description:"CPU share in thousandths of a core"` 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"` VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" description:"pod volumes to mount into the container's filesystem"`
...@@ -546,6 +546,13 @@ type NodeResources struct { ...@@ -546,6 +546,13 @@ type NodeResources struct {
type ResourceName string 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 type ResourceList map[ResourceName]util.IntOrString
// Minion is a worker node in Kubernetenes. // Minion is a worker node in Kubernetenes.
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package v1beta3 package v1beta3
import ( import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
...@@ -326,13 +327,13 @@ type Container struct { ...@@ -326,13 +327,13 @@ type Container struct {
WorkingDir string `json:"workingDir,omitempty"` WorkingDir string `json:"workingDir,omitempty"`
Ports []Port `json:"ports,omitempty"` Ports []Port `json:"ports,omitempty"`
Env []EnvVar `json:"env,omitempty"` Env []EnvVar `json:"env,omitempty"`
// Optional: Defaults to unlimited. // Optional: Defaults to unlimited. Units: bytes.
Memory int `json:"memory,omitempty"` Memory resource.Quantity `json:"memory,omitempty"`
// Optional: Defaults to unlimited. // Optional: Defaults to unlimited. Units: Cores. (500m == 1/2 core)
CPU int `json:"cpu,omitempty"` CPU resource.Quantity `json:"cpu,omitempty"`
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"`
LivenessProbe *LivenessProbe `json:"livenessProbe,omitempty"` LivenessProbe *LivenessProbe `json:"livenessProbe,omitempty"`
Lifecycle *Lifecycle `json:"lifecycle,omitempty"` Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
// Optional: Defaults to /dev/termination-log // Optional: Defaults to /dev/termination-log
TerminationMessagePath string `json:"terminationMessagePath,omitempty"` TerminationMessagePath string `json:"terminationMessagePath,omitempty"`
// Optional: Default to false. // Optional: Default to false.
...@@ -776,9 +777,18 @@ type NodeCondition struct { ...@@ -776,9 +777,18 @@ type NodeCondition struct {
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
} }
// ResourceName is the name identifying various resources in a ResourceList.
type ResourceName string 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. // Node is a worker node in Kubernetes.
// The name of the node according to etcd is in ID. // The name of the node according to etcd is in ID.
......
...@@ -18,7 +18,6 @@ package validation ...@@ -18,7 +18,6 @@ package validation
import ( import (
"fmt" "fmt"
"reflect"
"strings" "strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
...@@ -429,7 +428,7 @@ func ValidatePodUpdate(newPod, oldPod *api.Pod) errs.ValidationErrorList { ...@@ -429,7 +428,7 @@ func ValidatePodUpdate(newPod, oldPod *api.Pod) errs.ValidationErrorList {
newContainers = append(newContainers, container) newContainers = append(newContainers, container)
} }
pod.Spec.Containers = newContainers 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. // TODO: a better error would include all immutable fields explicitly.
allErrs = append(allErrs, errs.NewFieldInvalid("spec.containers", newPod.Spec.Containers, "some fields are immutable")) 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 { ...@@ -586,7 +585,7 @@ func ValidateMinion(minion *api.Node) errs.ValidationErrorList {
func ValidateMinionUpdate(oldMinion *api.Node, minion *api.Node) errs.ValidationErrorList { func ValidateMinionUpdate(oldMinion *api.Node, minion *api.Node) errs.ValidationErrorList {
allErrs := 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")) 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 ...@@ -596,7 +595,7 @@ func ValidateMinionUpdate(oldMinion *api.Node, minion *api.Node) errs.Validation
// Clear status // Clear status
oldMinion.Status = minion.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) 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")) allErrs = append(allErrs, fmt.Errorf("update contains more than labels or capacity changes"))
} }
......
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "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/capabilities"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
...@@ -372,8 +373,8 @@ func TestValidateManifest(t *testing.T) { ...@@ -372,8 +373,8 @@ func TestValidateManifest(t *testing.T) {
Image: "image", Image: "image",
Command: []string{"foo", "bar"}, Command: []string{"foo", "bar"},
WorkingDir: "/tmp", WorkingDir: "/tmp",
Memory: 1, Memory: resource.MustParse("1"),
CPU: 1, CPU: resource.MustParse("1"),
Ports: []api.Port{ Ports: []api.Port{
{Name: "p1", ContainerPort: 80, HostPort: 8080}, {Name: "p1", ContainerPort: 80, HostPort: 8080},
{Name: "p2", ContainerPort: 81}, {Name: "p2", ContainerPort: 81},
...@@ -623,7 +624,7 @@ func TestValidatePodUpdate(t *testing.T) { ...@@ -623,7 +624,7 @@ func TestValidatePodUpdate(t *testing.T) {
Containers: []api.Container{ Containers: []api.Container{
{ {
Image: "foo:V1", Image: "foo:V1",
CPU: 100, CPU: resource.MustParse("100m"),
}, },
}, },
}, },
...@@ -634,7 +635,7 @@ func TestValidatePodUpdate(t *testing.T) { ...@@ -634,7 +635,7 @@ func TestValidatePodUpdate(t *testing.T) {
Containers: []api.Container{ Containers: []api.Container{
{ {
Image: "foo:V2", Image: "foo:V2",
CPU: 1000, CPU: resource.MustParse("1000m"),
}, },
}, },
}, },
...@@ -1300,8 +1301,8 @@ func TestValidateMinionUpdate(t *testing.T) { ...@@ -1300,8 +1301,8 @@ func TestValidateMinionUpdate(t *testing.T) {
}, },
Spec: api.NodeSpec{ Spec: api.NodeSpec{
Capacity: api.ResourceList{ Capacity: api.ResourceList{
"cpu": util.NewIntOrStringFromInt(10000), api.ResourceCPU: resource.MustParse("10000"),
"memory": util.NewIntOrStringFromInt(100), api.ResourceMemory: resource.MustParse("100"),
}, },
}, },
}, api.Node{ }, api.Node{
...@@ -1310,8 +1311,8 @@ func TestValidateMinionUpdate(t *testing.T) { ...@@ -1310,8 +1311,8 @@ func TestValidateMinionUpdate(t *testing.T) {
}, },
Spec: api.NodeSpec{ Spec: api.NodeSpec{
Capacity: api.ResourceList{ Capacity: api.ResourceList{
"cpu": util.NewIntOrStringFromInt(100), api.ResourceCPU: resource.MustParse("100"),
"memory": util.NewIntOrStringFromInt(10000), api.ResourceMemory: resource.MustParse("10000"),
}, },
}, },
}, true}, }, true},
...@@ -1322,8 +1323,8 @@ func TestValidateMinionUpdate(t *testing.T) { ...@@ -1322,8 +1323,8 @@ func TestValidateMinionUpdate(t *testing.T) {
}, },
Spec: api.NodeSpec{ Spec: api.NodeSpec{
Capacity: api.ResourceList{ Capacity: api.ResourceList{
"cpu": util.NewIntOrStringFromInt(10000), api.ResourceCPU: resource.MustParse("10000"),
"memory": util.NewIntOrStringFromInt(100), api.ResourceMemory: resource.MustParse("100"),
}, },
}, },
}, api.Node{ }, api.Node{
...@@ -1333,8 +1334,8 @@ func TestValidateMinionUpdate(t *testing.T) { ...@@ -1333,8 +1334,8 @@ func TestValidateMinionUpdate(t *testing.T) {
}, },
Spec: api.NodeSpec{ Spec: api.NodeSpec{
Capacity: api.ResourceList{ Capacity: api.ResourceList{
"cpu": util.NewIntOrStringFromInt(100), api.ResourceCPU: resource.MustParse("100"),
"memory": util.NewIntOrStringFromInt(10000), api.ResourceMemory: resource.MustParse("10000"),
}, },
}, },
}, true}, }, true},
......
...@@ -28,9 +28,9 @@ import ( ...@@ -28,9 +28,9 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "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/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resources"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version" "github.com/GoogleCloudPlatform/kubernetes/pkg/version"
...@@ -96,7 +96,7 @@ func (c *testClient) Setup() *testClient { ...@@ -96,7 +96,7 @@ func (c *testClient) Setup() *testClient {
func (c *testClient) Validate(t *testing.T, received runtime.Object, err error) { func (c *testClient) Validate(t *testing.T, received runtime.Object, err error) {
c.ValidateCommon(t, err) 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) 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) { ...@@ -734,8 +734,8 @@ func TestCreateMinion(t *testing.T) {
}, },
Spec: api.NodeSpec{ Spec: api.NodeSpec{
Capacity: api.ResourceList{ Capacity: api.ResourceList{
resources.CPU: util.NewIntOrStringFromInt(1000), api.ResourceCPU: resource.MustParse("1000m"),
resources.Memory: util.NewIntOrStringFromInt(1024 * 1024), api.ResourceMemory: resource.MustParse("1Mi"),
}, },
}, },
} }
......
...@@ -28,13 +28,13 @@ import ( ...@@ -28,13 +28,13 @@ import (
"strings" "strings"
"time" "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" "code.google.com/p/goauth2/compute/serviceaccount"
compute "code.google.com/p/google-api-go-client/compute/v1" compute "code.google.com/p/google-api-go-client/compute/v1"
container "code.google.com/p/google-api-go-client/container/v1beta1" 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" "github.com/golang/glog"
) )
...@@ -338,11 +338,12 @@ func (gce *GCECloud) List(filter string) ([]string, error) { ...@@ -338,11 +338,12 @@ func (gce *GCECloud) List(filter string) ([]string, error) {
return instances, nil 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{ return &api.NodeResources{
Capacity: api.ResourceList{ Capacity: api.ResourceList{
resources.CPU: util.NewIntOrStringFromInt(int(cpu * 1000)), api.ResourceCPU: *resource.NewMilliQuantity(int64(cpu*1000), resource.DecimalSI),
resources.Memory: util.NewIntOrStringFromInt(int(memory * 1024 * 1024 * 1024)), api.ResourceMemory: *resource.NewQuantity(int64(memory*1024*1024*1024), resource.BinarySI),
}, },
} }
} }
...@@ -359,6 +360,7 @@ func (gce *GCECloud) GetNodeResources(name string) (*api.NodeResources, error) { ...@@ -359,6 +360,7 @@ func (gce *GCECloud) GetNodeResources(name string) (*api.NodeResources, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO: actually read machine size instead of this awful hack.
switch canonicalizeMachineType(res.MachineType) { switch canonicalizeMachineType(res.MachineType) {
case "f1-micro": case "f1-micro":
return makeResources(1, 0.6), nil return makeResources(1, 0.6), nil
......
...@@ -36,8 +36,8 @@ import ( ...@@ -36,8 +36,8 @@ import (
"github.com/rackspace/gophercloud/pagination" "github.com/rackspace/gophercloud/pagination"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/golang/glog" "github.com/golang/glog"
) )
...@@ -169,11 +169,11 @@ func (os *OpenStack) Instances() (cloudprovider.Instances, bool) { ...@@ -169,11 +169,11 @@ func (os *OpenStack) Instances() (cloudprovider.Instances, bool) {
for _, flavor := range flavorList { for _, flavor := range flavorList {
rsrc := api.NodeResources{ rsrc := api.NodeResources{
Capacity: api.ResourceList{ Capacity: api.ResourceList{
"cpu": util.NewIntOrStringFromInt(flavor.VCPUs), api.ResourceCPU: *resource.NewMilliQuantity(int64(flavor.VCPUs*1000), resource.DecimalSI),
"memory": util.NewIntOrStringFromString(fmt.Sprintf("%dMiB", flavor.RAM)), api.ResourceMemory: resource.MustParse(fmt.Sprintf("%dMi", flavor.RAM)),
"openstack.org/disk": util.NewIntOrStringFromString(fmt.Sprintf("%dGB", flavor.Disk)), "openstack.org/disk": resource.MustParse(fmt.Sprintf("%dG", flavor.Disk)),
"openstack.org/rxTxFactor": util.NewIntOrStringFromInt(int(flavor.RxTxFactor * 1000)), "openstack.org/rxTxFactor": *resource.NewQuantity(int64(flavor.RxTxFactor*1000), resource.DecimalSI),
"openstack.org/swap": util.NewIntOrStringFromString(fmt.Sprintf("%dMiB", flavor.Swap)), "openstack.org/swap": resource.MustParse(fmt.Sprintf("%dMi", flavor.Swap)),
}, },
} }
flavor_to_resource[flavor.ID] = &rsrc flavor_to_resource[flavor.ID] = &rsrc
......
...@@ -21,7 +21,6 @@ import ( ...@@ -21,7 +21,6 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"path" "path"
"reflect"
"sync" "sync"
"testing" "testing"
"time" "time"
...@@ -233,7 +232,7 @@ func TestCreateReplica(t *testing.T) { ...@@ -233,7 +232,7 @@ func TestCreateReplica(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Unexpected error: %#v", err) t.Errorf("Unexpected error: %#v", err)
} }
if !reflect.DeepEqual(&expectedPod, actualPod) { if !api.Semantic.DeepEqual(&expectedPod, actualPod) {
t.Logf("Body: %s", fakeHandler.RequestBody) t.Logf("Body: %s", fakeHandler.RequestBody)
t.Errorf("Unexpected mismatch. Expected\n %#v,\n Got:\n %#v", &expectedPod, actualPod) t.Errorf("Unexpected mismatch. Expected\n %#v,\n Got:\n %#v", &expectedPod, actualPod)
} }
...@@ -345,7 +344,7 @@ func TestWatchControllers(t *testing.T) { ...@@ -345,7 +344,7 @@ func TestWatchControllers(t *testing.T) {
var testControllerSpec api.ReplicationController var testControllerSpec api.ReplicationController
received := make(chan struct{}) received := make(chan struct{})
manager.syncHandler = func(controllerSpec api.ReplicationController) error { 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) t.Errorf("Expected %#v, but got %#v", testControllerSpec, controllerSpec)
} }
close(received) close(received)
......
...@@ -22,15 +22,28 @@ import ( ...@@ -22,15 +22,28 @@ import (
func TestEqualities(t *testing.T) { func TestEqualities(t *testing.T) {
e := Equalities{} e := Equalities{}
type Bar struct {
X int
}
type Baz struct {
Y Bar
}
err := e.AddFuncs( err := e.AddFuncs(
func(a, b int) bool { func(a, b int) bool {
return a+1 == b return a+1 == b
}, },
func(a, b Bar) bool {
return a.X*10 == b.X
},
) )
if err != nil { if err != nil {
t.Fatalf("Unexpected: %v", err) t.Fatalf("Unexpected: %v", err)
} }
type Foo struct {
X int
}
table := []struct { table := []struct {
a, b interface{} a, b interface{}
equal bool equal bool
...@@ -38,6 +51,10 @@ func TestEqualities(t *testing.T) { ...@@ -38,6 +51,10 @@ func TestEqualities(t *testing.T) {
{1, 2, true}, {1, 2, true},
{2, 1, false}, {2, 1, false},
{"foo", "foo", true}, {"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{"foo": 1}, map[string]int{"foo": 2}, true},
{map[string]int{}, map[string]int(nil), true}, {map[string]int{}, map[string]int(nil), true},
{[]int{}, []int(nil), true}, {[]int{}, []int(nil), true},
......
...@@ -17,7 +17,6 @@ limitations under the License. ...@@ -17,7 +17,6 @@ limitations under the License.
package config package config
import ( import (
"reflect"
"sort" "sort"
"testing" "testing"
...@@ -65,13 +64,7 @@ func CreateValidPod(name, namespace, source string) api.BoundPod { ...@@ -65,13 +64,7 @@ func CreateValidPod(name, namespace, source string) api.BoundPod {
} }
func CreatePodUpdate(op kubelet.PodOperation, source string, pods ...api.BoundPod) kubelet.PodUpdate { 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)) newPods := make([]api.BoundPod, len(pods))
if len(pods) == 0 {
return kubelet.PodUpdate{newPods, op, source}
}
for i := range pods { for i := range pods {
newPods[i] = pods[i] newPods[i] = pods[i]
} }
...@@ -89,7 +82,7 @@ func expectPodUpdate(t *testing.T, ch <-chan kubelet.PodUpdate, expected ...kube ...@@ -89,7 +82,7 @@ func expectPodUpdate(t *testing.T, ch <-chan kubelet.PodUpdate, expected ...kube
for i := range expected { for i := range expected {
update := <-ch update := <-ch
sort.Sort(sortedPods(update.Pods)) 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) t.Fatalf("Expected %#v, Got %#v", expected[i], update)
} }
} }
......
...@@ -20,7 +20,6 @@ import ( ...@@ -20,7 +20,6 @@ import (
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"os" "os"
"reflect"
"sort" "sort"
"testing" "testing"
"time" "time"
...@@ -130,7 +129,7 @@ func TestReadFromFile(t *testing.T) { ...@@ -130,7 +129,7 @@ func TestReadFromFile(t *testing.T) {
Containers: []api.Container{{Image: "test/image", TerminationMessagePath: "/dev/termination-log"}}, 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) t.Fatalf("Expected %#v, Got %#v", expected, update)
} }
...@@ -171,7 +170,7 @@ func TestExtractFromValidDataFile(t *testing.T) { ...@@ -171,7 +170,7 @@ func TestExtractFromValidDataFile(t *testing.T) {
} }
update := (<-ch).(kubelet.PodUpdate) update := (<-ch).(kubelet.PodUpdate)
expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource, expectedPod) 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) t.Errorf("Expected %#v, Got %#v", expected, update)
} }
} }
...@@ -192,7 +191,7 @@ func TestExtractFromEmptyDir(t *testing.T) { ...@@ -192,7 +191,7 @@ func TestExtractFromEmptyDir(t *testing.T) {
update := (<-ch).(kubelet.PodUpdate) update := (<-ch).(kubelet.PodUpdate)
expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource) 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) t.Errorf("Expected %#v, Got %#v", expected, update)
} }
} }
...@@ -242,7 +241,7 @@ func TestExtractFromDir(t *testing.T) { ...@@ -242,7 +241,7 @@ func TestExtractFromDir(t *testing.T) {
expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource, pods...) expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource, pods...)
sort.Sort(sortedPods(update.Pods)) sort.Sort(sortedPods(update.Pods))
sort.Sort(sortedPods(expected.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) t.Fatalf("Expected %#v, Got %#v", expected, update)
} }
for i := range update.Pods { for i := range update.Pods {
......
...@@ -19,7 +19,6 @@ package config ...@@ -19,7 +19,6 @@ package config
import ( import (
"encoding/json" "encoding/json"
"net/http/httptest" "net/http/httptest"
"reflect"
"testing" "testing"
"time" "time"
...@@ -193,7 +192,7 @@ func TestExtractFromHTTP(t *testing.T) { ...@@ -193,7 +192,7 @@ func TestExtractFromHTTP(t *testing.T) {
continue continue
} }
update := (<-ch).(kubelet.PodUpdate) 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) t.Errorf("%s: Expected: %#v, Got: %#v", testCase.desc, testCase.expected, update)
} }
for i := range update.Pods { for i := range update.Pods {
......
...@@ -398,7 +398,7 @@ func makePortsAndBindings(container *api.Container) (map[docker.Port]struct{}, m ...@@ -398,7 +398,7 @@ func makePortsAndBindings(container *api.Container) (map[docker.Port]struct{}, m
return exposedPorts, portBindings return exposedPorts, portBindings
} }
func milliCPUToShares(milliCPU int) int { func milliCPUToShares(milliCPU int64) int64 {
if milliCPU == 0 { if milliCPU == 0 {
// zero milliCPU means unset. Use kernel default. // zero milliCPU means unset. Use kernel default.
return 0 return 0
...@@ -537,8 +537,8 @@ func (kl *Kubelet) runContainer(pod *api.BoundPod, container *api.Container, pod ...@@ -537,8 +537,8 @@ func (kl *Kubelet) runContainer(pod *api.BoundPod, container *api.Container, pod
ExposedPorts: exposedPorts, ExposedPorts: exposedPorts,
Hostname: pod.Name, Hostname: pod.Name,
Image: container.Image, Image: container.Image,
Memory: int64(container.Memory), Memory: container.Memory.Value(),
CPUShares: int64(milliCPUToShares(container.CPU)), CPUShares: milliCPUToShares(container.CPU.MilliValue()),
WorkingDir: container.WorkingDir, WorkingDir: container.WorkingDir,
}, },
} }
......
...@@ -20,7 +20,6 @@ import ( ...@@ -20,7 +20,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"reflect"
"strings" "strings"
"testing" "testing"
"time" "time"
...@@ -136,7 +135,7 @@ func TestControllerDecode(t *testing.T) { ...@@ -136,7 +135,7 @@ func TestControllerDecode(t *testing.T) {
t.Errorf("unexpected error: %v", err) 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) t.Errorf("Expected %#v, found %#v", controller, controllerOut)
} }
} }
...@@ -208,7 +207,7 @@ func TestControllerParsing(t *testing.T) { ...@@ -208,7 +207,7 @@ func TestControllerParsing(t *testing.T) {
t.Errorf("unexpected error: %v", err) 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) t.Errorf("Parsing failed: %s %#v %#v", string(data), controller, expectedController)
} }
} }
...@@ -375,7 +374,7 @@ func TestFillCurrentState(t *testing.T) { ...@@ -375,7 +374,7 @@ func TestFillCurrentState(t *testing.T) {
if controller.Status.Replicas != 2 { if controller.Status.Replicas != 2 {
t.Errorf("expected 2, got: %d", controller.Status.Replicas) 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) t.Errorf("unexpected output: %#v %#v", labels.Set(controller.Spec.Selector).AsSelector(), fakeLister.s)
} }
} }
......
...@@ -448,7 +448,7 @@ func TestEtcdUpdatePodNotScheduled(t *testing.T) { ...@@ -448,7 +448,7 @@ func TestEtcdUpdatePodNotScheduled(t *testing.T) {
} }
var podOut api.Pod var podOut api.Pod
latest.Codec.DecodeInto([]byte(response.Node.Value), &podOut) 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) t.Errorf("expected: %v, got: %v", podOut, podIn)
} }
} }
...@@ -529,7 +529,7 @@ func TestEtcdUpdatePodScheduled(t *testing.T) { ...@@ -529,7 +529,7 @@ func TestEtcdUpdatePodScheduled(t *testing.T) {
} }
var podOut api.Pod var podOut api.Pod
latest.Codec.DecodeInto([]byte(response.Node.Value), &podOut) 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) t.Errorf("expected: %#v, got: %#v", podOut, podIn)
} }
...@@ -542,7 +542,7 @@ func TestEtcdUpdatePodScheduled(t *testing.T) { ...@@ -542,7 +542,7 @@ func TestEtcdUpdatePodScheduled(t *testing.T) {
t.Fatalf("unexpected error decoding response: %v", err) 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) t.Errorf("unexpected container list: %d\n items[0] - %#v\n podin.spec - %#v\n", len(list.Items), list.Items[0].Spec, podIn.Spec)
} }
} }
......
/*
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
/*
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
}
/*
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)
}
}
}
...@@ -22,7 +22,6 @@ import ( ...@@ -22,7 +22,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resources"
"github.com/golang/glog" "github.com/golang/glog"
) )
...@@ -89,15 +88,15 @@ type ResourceFit struct { ...@@ -89,15 +88,15 @@ type ResourceFit struct {
} }
type resourceRequest struct { type resourceRequest struct {
milliCPU int milliCPU int64
memory int memory int64
} }
func getResourceRequest(pod *api.Pod) resourceRequest { func getResourceRequest(pod *api.Pod) resourceRequest {
result := resourceRequest{} result := resourceRequest{}
for ix := range pod.Spec.Containers { for ix := range pod.Spec.Containers {
result.memory += pod.Spec.Containers[ix].Memory result.memory += pod.Spec.Containers[ix].Memory.Value()
result.milliCPU += pod.Spec.Containers[ix].CPU result.milliCPU += pod.Spec.Containers[ix].CPU.MilliValue()
} }
return result return result
} }
...@@ -113,17 +112,16 @@ func (r *ResourceFit) PodFitsResources(pod api.Pod, existingPods []api.Pod, node ...@@ -113,17 +112,16 @@ func (r *ResourceFit) PodFitsResources(pod api.Pod, existingPods []api.Pod, node
if err != nil { if err != nil {
return false, err return false, err
} }
milliCPURequested := 0 milliCPURequested := int64(0)
memoryRequested := 0 memoryRequested := int64(0)
for ix := range existingPods { for ix := range existingPods {
existingRequest := getResourceRequest(&existingPods[ix]) existingRequest := getResourceRequest(&existingPods[ix])
milliCPURequested += existingRequest.milliCPU milliCPURequested += existingRequest.milliCPU
memoryRequested += existingRequest.memory memoryRequested += existingRequest.memory
} }
// TODO: convert to general purpose resource matching, when pods ask for resources totalMilliCPU := info.Spec.Capacity.Get(api.ResourceCPU).MilliValue()
totalMilliCPU := int(resources.GetFloatResource(info.Spec.Capacity, resources.CPU, 0) * 1000) totalMemory := info.Spec.Capacity.Get(api.ResourceMemory).Value()
totalMemory := resources.GetIntegerResource(info.Spec.Capacity, resources.Memory, 0)
fitsCPU := totalMilliCPU == 0 || (totalMilliCPU-milliCPURequested) >= podRequest.milliCPU fitsCPU := totalMilliCPU == 0 || (totalMilliCPU-milliCPURequested) >= podRequest.milliCPU
fitsMemory := totalMemory == 0 || (totalMemory-memoryRequested) >= podRequest.memory fitsMemory := totalMemory == 0 || (totalMemory-memoryRequested) >= podRequest.memory
......
...@@ -21,8 +21,7 @@ import ( ...@@ -21,8 +21,7 @@ import (
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resources" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
type FakeNodeInfo api.Node type FakeNodeInfo api.Node
...@@ -32,17 +31,11 @@ func (n FakeNodeInfo) GetNodeInfo(nodeName string) (*api.Node, error) { ...@@ -32,17 +31,11 @@ func (n FakeNodeInfo) GetNodeInfo(nodeName string) (*api.Node, error) {
return &node, nil return &node, nil
} }
func makeResources(milliCPU int, memory int) api.NodeResources { func makeResources(milliCPU int64, memory int64) api.NodeResources {
return api.NodeResources{ return api.NodeResources{
Capacity: api.ResourceList{ Capacity: api.ResourceList{
resources.CPU: util.IntOrString{ api.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
IntVal: milliCPU, api.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI),
Kind: util.IntstrInt,
},
resources.Memory: util.IntOrString{
IntVal: memory,
Kind: util.IntstrInt,
},
}, },
} }
} }
...@@ -51,8 +44,8 @@ func newResourcePod(usage ...resourceRequest) api.Pod { ...@@ -51,8 +44,8 @@ func newResourcePod(usage ...resourceRequest) api.Pod {
containers := []api.Container{} containers := []api.Container{}
for _, req := range usage { for _, req := range usage {
containers = append(containers, api.Container{ containers = append(containers, api.Container{
Memory: req.memory, Memory: *resource.NewQuantity(req.memory, resource.BinarySI),
CPU: req.milliCPU, CPU: *resource.NewMilliQuantity(req.milliCPU, resource.DecimalSI),
}) })
} }
return api.Pod{ return api.Pod{
......
...@@ -18,13 +18,12 @@ package scheduler ...@@ -18,13 +18,12 @@ package scheduler
import ( import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resources"
"github.com/golang/glog" "github.com/golang/glog"
) )
// the unused capacity is calculated on a scale of 0-10 // the unused capacity is calculated on a scale of 0-10
// 0 being the lowest priority and 10 being the highest // 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 { if capacity == 0 {
return 0 return 0
} }
...@@ -32,30 +31,39 @@ func calculateScore(requested, capacity int, node string) int { ...@@ -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) glog.Errorf("Combined requested resources from existing pods exceeds capacity on minion: %s", node)
return 0 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. // 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. // 'pods' is a list of pods currently scheduled on the node.
func calculateOccupancy(pod api.Pod, node api.Node, pods []api.Pod) HostPriority { func calculateOccupancy(pod api.Pod, node api.Node, pods []api.Pod) HostPriority {
totalCPU := 0 totalMilliCPU := int64(0)
totalMemory := 0 totalMemory := int64(0)
for _, existingPod := range pods { for _, existingPod := range pods {
for _, container := range existingPod.Spec.Containers { for _, container := range existingPod.Spec.Containers {
totalCPU += container.CPU totalMilliCPU += container.CPU.MilliValue()
totalMemory += container.Memory totalMemory += container.Memory.Value()
} }
} }
// Add the resources requested by the current pod being scheduled. // Add the resources requested by the current pod being scheduled.
// This also helps differentiate between differently sized, but empty, minions. // This also helps differentiate between differently sized, but empty, minions.
for _, container := range pod.Spec.Containers { for _, container := range pod.Spec.Containers {
totalCPU += container.CPU totalMilliCPU += container.CPU.MilliValue()
totalMemory += container.Memory totalMemory += container.Memory.Value()
} }
cpuScore := calculateScore(totalCPU, resources.GetIntegerResource(node.Spec.Capacity, resources.CPU, 0), node.Name) capacityMilliCPU := node.Spec.Capacity.Get(api.ResourceCPU).MilliValue()
memoryScore := calculateScore(totalMemory, resources.GetIntegerResource(node.Spec.Capacity, resources.Memory, 0), node.Name) capacityMemory := node.Spec.Capacity.Get(api.ResourceMemory).Value()
glog.V(4).Infof("Least Requested Priority, AbsoluteRequested: (%d, %d) Score:(%d, %d)", totalCPU, totalMemory, cpuScore, memoryScore)
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{ return HostPriority{
host: node.Name, host: node.Name,
......
...@@ -21,17 +21,16 @@ import ( ...@@ -21,17 +21,16 @@ import (
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resources" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
func makeMinion(node string, cpu, memory int) api.Node { func makeMinion(node string, milliCPU, memory int64) api.Node {
return api.Node{ return api.Node{
ObjectMeta: api.ObjectMeta{Name: node}, ObjectMeta: api.ObjectMeta{Name: node},
Spec: api.NodeSpec{ Spec: api.NodeSpec{
Capacity: api.ResourceList{ Capacity: api.ResourceList{
resources.CPU: util.NewIntOrStringFromInt(cpu), api.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
resources.Memory: util.NewIntOrStringFromInt(memory), api.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI),
}, },
}, },
} }
...@@ -57,14 +56,14 @@ func TestLeastRequested(t *testing.T) { ...@@ -57,14 +56,14 @@ func TestLeastRequested(t *testing.T) {
} }
cpuOnly := api.PodSpec{ cpuOnly := api.PodSpec{
Containers: []api.Container{ Containers: []api.Container{
{CPU: 1000}, {CPU: resource.MustParse("1000m")},
{CPU: 2000}, {CPU: resource.MustParse("2000m")},
}, },
} }
cpuAndMemory := api.PodSpec{ cpuAndMemory := api.PodSpec{
Containers: []api.Container{ Containers: []api.Container{
{CPU: 1000, Memory: 2000}, {CPU: resource.MustParse("1000m"), Memory: resource.MustParse("2000")},
{CPU: 2000, Memory: 3000}, {CPU: resource.MustParse("2000m"), Memory: resource.MustParse("3000")},
}, },
} }
tests := []struct { tests := []struct {
......
...@@ -18,12 +18,12 @@ package standalone ...@@ -18,12 +18,12 @@ package standalone
import ( import (
"fmt" "fmt"
"math"
"net" "net"
"net/http" "net/http"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth" "github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth"
...@@ -33,7 +33,6 @@ import ( ...@@ -33,7 +33,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/config" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/config"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master" "github.com/GoogleCloudPlatform/kubernetes/pkg/master"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resources"
"github.com/GoogleCloudPlatform/kubernetes/pkg/service" "github.com/GoogleCloudPlatform/kubernetes/pkg/service"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
...@@ -125,22 +124,10 @@ func RunScheduler(cl *client.Client) { ...@@ -125,22 +124,10 @@ func RunScheduler(cl *client.Client) {
// RunControllerManager starts a controller // RunControllerManager starts a controller
func RunControllerManager(machineList []string, cl *client.Client, nodeMilliCPU, nodeMemory int64) { 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{ nodeResources := &api.NodeResources{
Capacity: api.ResourceList{ Capacity: api.ResourceList{
resources.CPU: util.NewIntOrStringFromInt(int(nodeMilliCPU)), api.ResourceCPU: *resource.NewMilliQuantity(nodeMilliCPU, resource.DecimalSI),
resources.Memory: util.NewIntOrStringFromInt(int(nodeMemory)), api.ResourceMemory: *resource.NewQuantity(nodeMemory, resource.BinarySI),
}, },
} }
minionController := minionControllerPkg.NewMinionController(nil, "", machineList, nodeResources, cl) minionController := minionControllerPkg.NewMinionController(nil, "", machineList, nodeResources, cl)
......
...@@ -113,6 +113,14 @@ func (intstr *IntOrString) UnmarshalJSON(value []byte) error { ...@@ -113,6 +113,14 @@ func (intstr *IntOrString) UnmarshalJSON(value []byte) error {
return json.Unmarshal(value, &intstr.IntVal) 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. // MarshalJSON implements the json.Marshaller interface.
func (intstr IntOrString) MarshalJSON() ([]byte, error) { func (intstr IntOrString) MarshalJSON() ([]byte, error) {
switch intstr.Kind { switch intstr.Kind {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment