Wire resource.Quantity into api

pull/6/head
Daniel Smith 2015-01-02 19:10:03 -08:00
parent 3b5c3ec786
commit 894a3e6d3f
8 changed files with 214 additions and 21 deletions

View File

@ -27,16 +27,28 @@ 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/conversion"
"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.")
// apiObjectComparer can do semantic deep equality checks for api objects.
var apiObjectComparer = conversion.EqualitiesOrDie(
func(a, b resource.Quantity) bool {
// Ignore formatting, only care that numeric value stayed the same.
return a.Amount.Cmp(b.Amount) == 0
},
)
// apiObjectFuzzer can randomly populate api objects. // apiObjectFuzzer can randomly populate api objects.
var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
func(j *runtime.PluginBase, c fuzz.Continue) { func(j *runtime.PluginBase, c fuzz.Continue) {
@ -136,6 +148,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 +181,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 !apiObjectComparer.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 +192,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 !apiObjectComparer.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 +266,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 !apiObjectComparer.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)
} }
} }

View File

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

View File

@ -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() {
// 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() {
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)
}
} }

View File

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

View File

@ -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() {
// 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() {
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)
}
} }

View File

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

View File

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

View File

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