Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add numeric type into api #3195

Merged
merged 6 commits into from
Jan 8, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 5 additions & 17 deletions cmd/kube-controller-manager/controller-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding QuantityFlag() itself: Should that continue to return *Quantity or just a value? And should it call MustParse() internally instead of panic()?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And any reason to have done it this way as opposed to, e.g., the way machineList is done?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QuantityFlag has to return a pointer, because it's going to modify the variable it returns when flag.Parse() is called.

I did it this way because it's just like the pattern that the flag package itself uses.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did make QuantityFlag use MustParse, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: Pointer - Duh! Sorry.

Re: style - We should make the other flag-adapter types follow the same pattern.

)

func init() {
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
22 changes: 22 additions & 0 deletions pkg/api/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions pkg/api/latest/latest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package latest

import (
"encoding/json"
"reflect"
"strconv"
"testing"

Expand Down Expand Up @@ -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))
}
}
Expand Down
17 changes: 7 additions & 10 deletions pkg/api/resource/quantity.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
10 changes: 5 additions & 5 deletions pkg/api/resource/quantity_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
19 changes: 16 additions & 3 deletions pkg/api/serialization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down Expand Up @@ -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) {
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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)
}
}
Expand Down
33 changes: 27 additions & 6 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
61 changes: 60 additions & 1 deletion pkg/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
}
4 changes: 2 additions & 2 deletions pkg/api/v1beta1/conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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)
}
}
Expand Down