Merge pull request #16964 from liggitt/json_precision

Automatic merge from submit-queue

Preserve int data when unmarshaling

There are several places we use `json.Unmarshal` into an unstructured map (StrategicMergePatch, UnstructuredJSONScheme, many others).

In this scenario, the json package converts all numbers to float64. This exposes many of the int64 fields in our API types to corruption when the unstructured map is marshalled back to json.

A simple example is a pod with an `"activeDeadlineSeconds": 1000000`. Trying to use `kubectl label`, `annotate`, `patch`, etc results in that int64 being converted to a float64, submitted to the server, and the server rejecting it with an error about "cannot unmarshal number 1e+6 into Go value of type int64"

The json package provides a way to defer conversion of numbers (`json.Decoder#UseNumber`), but does not actually do conversions to int or float. This PR makes use of that feature, and post-processes the unmarshalled map to convert json.Number objects to either int64 or float64 values
This commit is contained in:
k8s-merge-robot
2016-04-12 07:09:32 -07:00
8 changed files with 626 additions and 4 deletions

View File

@@ -17,10 +17,11 @@ limitations under the License.
package runtime
import (
"encoding/json"
gojson "encoding/json"
"io"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/util/json"
)
// UnstructuredJSONScheme is capable of converting JSON data into the Unstructured
@@ -77,7 +78,7 @@ func (unstructuredJSONScheme) EncodeToStream(obj Object, w io.Writer, overrides
func (s unstructuredJSONScheme) decode(data []byte) (Object, error) {
type detector struct {
Items json.RawMessage
Items gojson.RawMessage
}
var det detector
if err := json.Unmarshal(data, &det); err != nil {
@@ -139,7 +140,7 @@ func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstru
func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList) error {
type decodeList struct {
TypeMeta `json:",inline"`
Items []json.RawMessage
Items []gojson.RawMessage
}
var dList decodeList

View File

@@ -19,10 +19,12 @@ package runtime_test
import (
"fmt"
"reflect"
"strings"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/runtime"
)
@@ -129,3 +131,66 @@ func TestDecode(t *testing.T) {
}
}
}
func TestDecodeNumbers(t *testing.T) {
// Start with a valid pod
originalJSON := []byte(`{
"kind":"Pod",
"apiVersion":"v1",
"metadata":{"name":"pod","namespace":"foo"},
"spec":{
"containers":[{"name":"container","image":"container"}],
"activeDeadlineSeconds":1000030003
}
}`)
pod := &api.Pod{}
// Decode with structured codec
codec, err := testapi.GetCodecForObject(pod)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = runtime.DecodeInto(codec, originalJSON, pod)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// ensure pod is valid
if errs := validation.ValidatePod(pod); len(errs) > 0 {
t.Fatalf("pod should be valid: %v", errs)
}
// Round-trip with unstructured codec
unstructuredObj, err := runtime.Decode(runtime.UnstructuredJSONScheme, originalJSON)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
roundtripJSON, err := runtime.Encode(runtime.UnstructuredJSONScheme, unstructuredObj)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Make sure we serialize back out in int form
if !strings.Contains(string(roundtripJSON), `"activeDeadlineSeconds":1000030003`) {
t.Errorf("Expected %s, got %s", `"activeDeadlineSeconds":1000030003`, string(roundtripJSON))
}
// Decode with structured codec again
obj2, err := runtime.Decode(codec, roundtripJSON)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// ensure pod is still valid
pod2, ok := obj2.(*api.Pod)
if !ok {
t.Fatalf("expected an *api.Pod, got %#v", obj2)
}
if errs := validation.ValidatePod(pod2); len(errs) > 0 {
t.Fatalf("pod should be valid: %v", errs)
}
// ensure round-trip preserved large integers
if !reflect.DeepEqual(pod, pod2) {
t.Fatalf("Expected\n\t%#v, got \n\t%#v", pod, pod2)
}
}