unstructured_test.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package testing
  14. import (
  15. "math/rand"
  16. "reflect"
  17. "sort"
  18. "testing"
  19. fuzz "github.com/google/gofuzz"
  20. jsoniter "github.com/json-iterator/go"
  21. v1 "k8s.io/api/core/v1"
  22. "k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
  23. apiequality "k8s.io/apimachinery/pkg/api/equality"
  24. "k8s.io/apimachinery/pkg/api/meta"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. metaunstruct "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  27. "k8s.io/apimachinery/pkg/runtime"
  28. "k8s.io/apimachinery/pkg/runtime/schema"
  29. "k8s.io/apimachinery/pkg/util/diff"
  30. "k8s.io/apimachinery/pkg/util/json"
  31. "k8s.io/kubernetes/pkg/api/legacyscheme"
  32. api "k8s.io/kubernetes/pkg/apis/core"
  33. )
  34. func doRoundTrip(t *testing.T, internalVersion schema.GroupVersion, externalVersion schema.GroupVersion, kind string) {
  35. // We do fuzzing on the internal version of the object, and only then
  36. // convert to the external version. This is because custom fuzzing
  37. // function are only supported for internal objects.
  38. internalObj, err := legacyscheme.Scheme.New(internalVersion.WithKind(kind))
  39. if err != nil {
  40. t.Fatalf("Couldn't create internal object %v: %v", kind, err)
  41. }
  42. seed := rand.Int63()
  43. fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), legacyscheme.Codecs).
  44. // We are explicitly overwriting custom fuzzing functions, to ensure
  45. // that InitContainers and their statuses are not generated. This is
  46. // because in this test we are simply doing json operations, in which
  47. // those disappear.
  48. Funcs(
  49. func(s *api.PodSpec, c fuzz.Continue) {
  50. c.FuzzNoCustom(s)
  51. s.InitContainers = nil
  52. },
  53. func(s *api.PodStatus, c fuzz.Continue) {
  54. c.FuzzNoCustom(s)
  55. s.InitContainerStatuses = nil
  56. },
  57. ).Fuzz(internalObj)
  58. item, err := legacyscheme.Scheme.New(externalVersion.WithKind(kind))
  59. if err != nil {
  60. t.Fatalf("Couldn't create external object %v: %v", kind, err)
  61. }
  62. if err := legacyscheme.Scheme.Convert(internalObj, item, nil); err != nil {
  63. t.Fatalf("Conversion for %v failed: %v", kind, err)
  64. }
  65. data, err := json.Marshal(item)
  66. if err != nil {
  67. t.Errorf("Error when marshaling object: %v", err)
  68. return
  69. }
  70. unstr := make(map[string]interface{})
  71. err = json.Unmarshal(data, &unstr)
  72. if err != nil {
  73. t.Errorf("Error when unmarshaling to unstructured: %v", err)
  74. return
  75. }
  76. data, err = json.Marshal(unstr)
  77. if err != nil {
  78. t.Errorf("Error when marshaling unstructured: %v", err)
  79. return
  80. }
  81. unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
  82. err = json.Unmarshal(data, &unmarshalledObj)
  83. if err != nil {
  84. t.Errorf("Error when unmarshaling to object: %v", err)
  85. return
  86. }
  87. if !apiequality.Semantic.DeepEqual(item, unmarshalledObj) {
  88. t.Errorf("Object changed during JSON operations, diff: %v", diff.ObjectReflectDiff(item, unmarshalledObj))
  89. return
  90. }
  91. newUnstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item)
  92. if err != nil {
  93. t.Errorf("ToUnstructured failed: %v", err)
  94. return
  95. }
  96. newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
  97. err = runtime.DefaultUnstructuredConverter.FromUnstructured(newUnstr, newObj)
  98. if err != nil {
  99. t.Errorf("FromUnstructured failed: %v", err)
  100. return
  101. }
  102. if !apiequality.Semantic.DeepEqual(item, newObj) {
  103. t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(item, newObj))
  104. }
  105. }
  106. func TestRoundTrip(t *testing.T) {
  107. for gvk := range legacyscheme.Scheme.AllKnownTypes() {
  108. if nonRoundTrippableTypes.Has(gvk.Kind) {
  109. continue
  110. }
  111. if gvk.Version == runtime.APIVersionInternal {
  112. continue
  113. }
  114. t.Logf("Testing: %v in %v", gvk.Kind, gvk.GroupVersion().String())
  115. for i := 0; i < 50; i++ {
  116. doRoundTrip(t, schema.GroupVersion{Group: gvk.Group, Version: runtime.APIVersionInternal}, gvk.GroupVersion(), gvk.Kind)
  117. if t.Failed() {
  118. break
  119. }
  120. }
  121. }
  122. }
  123. func TestRoundTripWithEmptyCreationTimestamp(t *testing.T) {
  124. for gvk := range legacyscheme.Scheme.AllKnownTypes() {
  125. if nonRoundTrippableTypes.Has(gvk.Kind) {
  126. continue
  127. }
  128. if gvk.Version == runtime.APIVersionInternal {
  129. continue
  130. }
  131. item, err := legacyscheme.Scheme.New(gvk)
  132. if err != nil {
  133. t.Fatalf("Couldn't create external object %v: %v", gvk, err)
  134. }
  135. t.Logf("Testing: %v in %v", gvk.Kind, gvk.GroupVersion().String())
  136. unstrBody, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item)
  137. if err != nil {
  138. t.Fatalf("ToUnstructured failed: %v", err)
  139. }
  140. unstructObj := &metaunstruct.Unstructured{}
  141. unstructObj.Object = unstrBody
  142. if meta, err := meta.Accessor(unstructObj); err == nil {
  143. meta.SetCreationTimestamp(metav1.Time{})
  144. } else {
  145. t.Fatalf("Unable to set creation timestamp: %v", err)
  146. }
  147. // attempt to re-convert unstructured object - conversion should not fail
  148. // based on empty metadata fields, such as creationTimestamp
  149. newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
  150. err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructObj.Object, newObj)
  151. if err != nil {
  152. t.Fatalf("FromUnstructured failed: %v", err)
  153. }
  154. }
  155. }
  156. func BenchmarkToUnstructured(b *testing.B) {
  157. items := benchmarkItems(b)
  158. size := len(items)
  159. convertor := runtime.DefaultUnstructuredConverter
  160. b.ResetTimer()
  161. for i := 0; i < b.N; i++ {
  162. unstr, err := convertor.ToUnstructured(&items[i%size])
  163. if err != nil || unstr == nil {
  164. b.Fatalf("unexpected error: %v", err)
  165. }
  166. }
  167. b.StopTimer()
  168. }
  169. func BenchmarkFromUnstructured(b *testing.B) {
  170. items := benchmarkItems(b)
  171. convertor := runtime.DefaultUnstructuredConverter
  172. var unstr []map[string]interface{}
  173. for i := range items {
  174. item, err := convertor.ToUnstructured(&items[i])
  175. if err != nil || item == nil {
  176. b.Fatalf("unexpected error: %v", err)
  177. }
  178. unstr = append(unstr, item)
  179. }
  180. size := len(items)
  181. b.ResetTimer()
  182. for i := 0; i < b.N; i++ {
  183. obj := v1.Pod{}
  184. if err := convertor.FromUnstructured(unstr[i%size], &obj); err != nil {
  185. b.Fatalf("unexpected error: %v", err)
  186. }
  187. }
  188. b.StopTimer()
  189. }
  190. func BenchmarkToUnstructuredViaJSON(b *testing.B) {
  191. items := benchmarkItems(b)
  192. var data [][]byte
  193. for i := range items {
  194. item, err := json.Marshal(&items[i])
  195. if err != nil {
  196. b.Fatalf("unexpected error: %v", err)
  197. }
  198. data = append(data, item)
  199. }
  200. size := len(items)
  201. b.ResetTimer()
  202. for i := 0; i < b.N; i++ {
  203. unstr := map[string]interface{}{}
  204. if err := json.Unmarshal(data[i%size], &unstr); err != nil {
  205. b.Fatalf("unexpected error: %v", err)
  206. }
  207. }
  208. b.StopTimer()
  209. }
  210. func BenchmarkFromUnstructuredViaJSON(b *testing.B) {
  211. items := benchmarkItems(b)
  212. var unstr []map[string]interface{}
  213. for i := range items {
  214. data, err := json.Marshal(&items[i])
  215. if err != nil {
  216. b.Fatalf("unexpected error: %v", err)
  217. }
  218. item := map[string]interface{}{}
  219. if err := json.Unmarshal(data, &item); err != nil {
  220. b.Fatalf("unexpected error: %v", err)
  221. }
  222. unstr = append(unstr, item)
  223. }
  224. size := len(items)
  225. b.ResetTimer()
  226. for i := 0; i < b.N; i++ {
  227. item, err := json.Marshal(unstr[i%size])
  228. if err != nil {
  229. b.Fatalf("unexpected error: %v", err)
  230. }
  231. obj := v1.Pod{}
  232. if err := json.Unmarshal(item, &obj); err != nil {
  233. b.Fatalf("unexpected error: %v", err)
  234. }
  235. }
  236. b.StopTimer()
  237. }
  238. func BenchmarkToUnstructuredViaJSONIter(b *testing.B) {
  239. items := benchmarkItems(b)
  240. size := len(items)
  241. var keys []string
  242. for k := range jsonIterConfig {
  243. keys = append(keys, k)
  244. }
  245. sort.Strings(keys)
  246. for _, name := range keys {
  247. c := jsonIterConfig[name]
  248. b.Run(name, func(b *testing.B) {
  249. b.ResetTimer()
  250. for i := 0; i < b.N; i++ {
  251. data, err := c.Marshal(&items[i%size])
  252. if err != nil {
  253. b.Fatalf("unexpected error: %v", err)
  254. }
  255. unstr := map[string]interface{}{}
  256. if err := c.Unmarshal(data, &unstr); err != nil {
  257. b.Fatalf("unexpected error: %v", err)
  258. }
  259. }
  260. b.StopTimer()
  261. })
  262. }
  263. }
  264. var jsonIterConfig = map[string]jsoniter.API{
  265. "default": jsoniter.ConfigDefault,
  266. "fastest": jsoniter.ConfigFastest,
  267. "compat": jsoniter.ConfigCompatibleWithStandardLibrary,
  268. }
  269. func BenchmarkFromUnstructuredViaJSONIter(b *testing.B) {
  270. items := benchmarkItems(b)
  271. var unstr []map[string]interface{}
  272. for i := range items {
  273. data, err := jsoniter.Marshal(&items[i])
  274. if err != nil {
  275. b.Fatalf("unexpected error: %v", err)
  276. }
  277. item := map[string]interface{}{}
  278. if err := json.Unmarshal(data, &item); err != nil {
  279. b.Fatalf("unexpected error: %v", err)
  280. }
  281. unstr = append(unstr, item)
  282. }
  283. size := len(items)
  284. var keys []string
  285. for k := range jsonIterConfig {
  286. keys = append(keys, k)
  287. }
  288. sort.Strings(keys)
  289. for _, name := range keys {
  290. c := jsonIterConfig[name]
  291. b.Run(name, func(b *testing.B) {
  292. b.ResetTimer()
  293. for i := 0; i < b.N; i++ {
  294. item, err := c.Marshal(unstr[i%size])
  295. if err != nil {
  296. b.Fatalf("unexpected error: %v", err)
  297. }
  298. obj := v1.Pod{}
  299. if err := c.Unmarshal(item, &obj); err != nil {
  300. b.Fatalf("unexpected error: %v", err)
  301. }
  302. }
  303. b.StopTimer()
  304. })
  305. }
  306. }