From 975871e0a065bb567de5007efe3313bedd860dd0 Mon Sep 17 00:00:00 2001 From: Wojciech Tyczynski Date: Fri, 13 Nov 2015 14:38:38 +0100 Subject: [PATCH] JSON marshaling/unmarshaling of label Selector --- pkg/api/unversioned/selector.go | 70 ++++++++++++++ pkg/api/unversioned/selector_test.go | 139 +++++++++++++++++++++++++++ pkg/labels/selector.go | 2 + 3 files changed, 211 insertions(+) create mode 100644 pkg/api/unversioned/selector.go create mode 100644 pkg/api/unversioned/selector_test.go diff --git a/pkg/api/unversioned/selector.go b/pkg/api/unversioned/selector.go new file mode 100644 index 00000000000..f7959afb723 --- /dev/null +++ b/pkg/api/unversioned/selector.go @@ -0,0 +1,70 @@ +/* +Copyright 2015 The Kubernetes Authors 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 unversioned + +import ( + "encoding/json" + + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" +) + +// FieldSelector is a wrapper around fields.Selector that allows for +// Marshaling/Unmarshaling underlying selector. +type FieldSelector struct { + Selector fields.Selector +} + +func (sh FieldSelector) MarshalJSON() ([]byte, error) { + return json.Marshal(sh.Selector.String()) +} + +func (sh *FieldSelector) UnmarshalJSON(b []byte) error { + var unmarshalled string + if err := json.Unmarshal(b, &unmarshalled); err != nil { + return err + } + selector, err := fields.ParseSelector(unmarshalled) + if err != nil { + return err + } + sh.Selector = selector + return nil +} + +// LabelSelector is a wrapper around labels.Selector that allow for +// Marshaling/Unmarshaling underlying selector. +type LabelSelector struct { + Selector labels.Selector +} + +func (sh LabelSelector) MarshalJSON() ([]byte, error) { + return json.Marshal(sh.Selector.String()) +} + +func (sh *LabelSelector) UnmarshalJSON(b []byte) error { + var unmarshalled string + if err := json.Unmarshal(b, &unmarshalled); err != nil { + return err + } + selector, err := labels.Parse(unmarshalled) + if err != nil { + return err + } + sh.Selector = selector + return nil +} diff --git a/pkg/api/unversioned/selector_test.go b/pkg/api/unversioned/selector_test.go new file mode 100644 index 00000000000..355ecfab561 --- /dev/null +++ b/pkg/api/unversioned/selector_test.go @@ -0,0 +1,139 @@ +/* +Copyright 2015 The Kubernetes Authors 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 unversioned + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/ghodss/yaml" + "github.com/ugorji/go/codec" + + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" +) + +type FieldSelectorWrapper struct { + Holder FieldSelector `json:"holder"` +} + +func parseFieldSelector(t *testing.T, selector string) fields.Selector { + result, err := fields.ParseSelector(selector) + if err != nil { + t.Fatalf("Unexpected error: %#v", err) + } + return result +} + +func TestFieldSelectorMarshal(t *testing.T) { + cases := []struct { + selector fields.Selector + result string + }{ + {parseFieldSelector(t, ""), "{\"holder\":\"\"}"}, + {parseFieldSelector(t, "foo=bar"), "{\"holder\":\"foo=bar\"}"}, + {parseFieldSelector(t, "foo=bar,bar=foo"), "{\"holder\":\"bar=foo,foo=bar\"}"}, + {parseFieldSelector(t, "foo=bar,bar!=foo"), "{\"holder\":\"bar!=foo,foo=bar\"}"}, + } + + for _, c := range cases { + wrapper := FieldSelectorWrapper{FieldSelector{c.selector}} + marshalled, err := json.Marshal(&wrapper) + if err != nil { + t.Errorf("Unexpected error: %#v", err) + } + if string(marshalled) != c.result { + t.Errorf("Expected: %s, got: %s", c.result, string(marshalled)) + } + + var result FieldSelectorWrapper + if err := json.Unmarshal(marshalled, &result); err != nil { + t.Errorf("Unexpected error: %#v", err) + } + if !reflect.DeepEqual(result, wrapper) { + t.Errorf("Expected: %#v, got: %#v", wrapper, result) + } + + var yamlResult FieldSelectorWrapper + if err := yaml.Unmarshal(marshalled, &yamlResult); err != nil { + t.Errorf("Unexpected error: %#v", err) + } + if !reflect.DeepEqual(yamlResult, wrapper) { + t.Errorf("Expected: %#v, got: %#v", wrapper, yamlResult) + } + + var codecResult FieldSelectorWrapper + if err := codec.NewDecoderBytes(marshalled, new(codec.JsonHandle)).Decode(&codecResult); err != nil { + t.Errorf("Unexpected error: %#v", err) + } + if !reflect.DeepEqual(codecResult, wrapper) { + t.Errorf("Expected: %#v, got: %#v", wrapper, codecResult) + } + } +} + +type LabelSelectorWrapper struct { + Holder LabelSelector `json:"holder"` +} + +func TestSelectorMarshal(t *testing.T) { + cases := []struct { + input labels.Set + result string + }{ + {labels.Set(map[string]string{}), "{\"holder\":\"\"}"}, + {labels.Set(map[string]string{"foo": "bar"}), "{\"holder\":\"foo=bar\"}"}, + {labels.Set(map[string]string{"foo": "bar", "bar": "foo"}), "{\"holder\":\"bar=foo,foo=bar\"}"}, + } + + for _, c := range cases { + selector := c.input.AsSelector() + wrapper := LabelSelectorWrapper{LabelSelector{selector}} + marshalled, err := json.Marshal(&wrapper) + if err != nil { + t.Errorf("Unexpected error: %#v", err) + } + if string(marshalled) != c.result { + t.Errorf("Expected: %s, got: %s", c.result, string(marshalled)) + } + + var result LabelSelectorWrapper + if err := json.Unmarshal(marshalled, &result); err != nil { + t.Errorf("Unexpected error: %#v", err) + } + if !reflect.DeepEqual(result, wrapper) { + t.Errorf("Expected: %#v, got: %#v", wrapper, result) + } + + var yamlResult LabelSelectorWrapper + if err := yaml.Unmarshal(marshalled, &yamlResult); err != nil { + t.Errorf("Unexpected error: %#v", err) + } + if !reflect.DeepEqual(yamlResult, wrapper) { + t.Errorf("Expected: %#v, got: %#v", wrapper, yamlResult) + } + + var codecResult LabelSelectorWrapper + if err := codec.NewDecoderBytes(marshalled, new(codec.JsonHandle)).Decode(&codecResult); err != nil { + t.Errorf("Unexpected error: %#v", err) + } + if !reflect.DeepEqual(codecResult, wrapper) { + t.Errorf("Expected: %#v, got: %#v", wrapper, codecResult) + } + } +} diff --git a/pkg/labels/selector.go b/pkg/labels/selector.go index 8f6a3b5e64e..07a9bf90bce 100644 --- a/pkg/labels/selector.go +++ b/pkg/labels/selector.go @@ -729,5 +729,7 @@ func SelectorFromSet(ls Set) Selector { requirements = append(requirements, *r) } } + // sort to have deterministic string representation + sort.Sort(ByKey(requirements)) return LabelSelector(requirements) }