Merge pull request #2746 from cmicat/change-multi-cluster-resource-version-format
Resource Aggregation Proxy: make multi cluster ResourceVersion string stable
This commit is contained in:
commit
190e025364
|
@ -3,6 +3,7 @@ package store
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
@ -63,13 +64,66 @@ func (m *multiClusterResourceVersion) String() string {
|
||||||
if m.isZero {
|
if m.isZero {
|
||||||
return "0"
|
return "0"
|
||||||
}
|
}
|
||||||
|
// todo consider to separate this two scenarios:
|
||||||
|
// 1. client do not send ResourceVersion
|
||||||
|
// 2. client send ResourceVersion with empty cluster.
|
||||||
if len(m.rvs) == 0 {
|
if len(m.rvs) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
buf, _ := json.Marshal(&m.rvs)
|
buf := marshalRvs(m.rvs)
|
||||||
return base64.RawURLEncoding.EncodeToString(buf)
|
return base64.RawURLEncoding.EncodeToString(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func marshalRvs(rvs map[string]string) []byte {
|
||||||
|
// We must make sure the returned ResourceVersion string is stable, because client might use this string
|
||||||
|
// for equality comparison. But hashmap's key iteration order is not determined.
|
||||||
|
// So we can't use json encoding library directly.
|
||||||
|
// Instead, we convert the map to a slice, sort the slice by `Cluster` field, then manually build a string.
|
||||||
|
// The result string resembles json encoding result to keep compatible with older version of proxy.
|
||||||
|
|
||||||
|
if len(rvs) == 0 {
|
||||||
|
// need to keep sync with `func (m *multiClusterResourceVersion) String() string`
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type onWireRvs struct {
|
||||||
|
Cluster string
|
||||||
|
ResourceVersion string
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]onWireRvs, 0, len(rvs))
|
||||||
|
|
||||||
|
for clusterName, version := range rvs {
|
||||||
|
obj := onWireRvs{clusterName, version}
|
||||||
|
slice = append(slice, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(slice, func(i, j int) bool {
|
||||||
|
return slice[i].Cluster < slice[j].Cluster
|
||||||
|
})
|
||||||
|
|
||||||
|
// Q: Why preallocate []byte with this capacity?
|
||||||
|
// A: Consider this json `{"cluster1":"1","cluster2":"2"}`
|
||||||
|
// For begin and end, need "{" and "}", this took 2 bytes.
|
||||||
|
// For `"cluster1":"1",`, there's 6 bytes. And we add 14bytes for longer cluster name and resource version.
|
||||||
|
var encoded = make([]byte, 0, (len(slice[0].Cluster)+len(slice[0].ResourceVersion)+20)*len(slice)+2)
|
||||||
|
encoded = append(encoded, '{')
|
||||||
|
for i, n := 0, len(slice); i < n; i++ {
|
||||||
|
encoded = append(encoded, '"')
|
||||||
|
encoded = append(encoded, slice[i].Cluster...)
|
||||||
|
encoded = append(encoded, `":"`...)
|
||||||
|
encoded = append(encoded, slice[i].ResourceVersion...)
|
||||||
|
encoded = append(encoded, '"')
|
||||||
|
|
||||||
|
if i != n-1 {
|
||||||
|
encoded = append(encoded, ',')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoded = append(encoded, '}')
|
||||||
|
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
type multiClusterContinue struct {
|
type multiClusterContinue struct {
|
||||||
Cluster string `json:"cluster,omitempty"`
|
Cluster string `json:"cluster,omitempty"`
|
||||||
Continue string `json:"continue,omitempty"`
|
Continue string `json:"continue,omitempty"`
|
||||||
|
|
|
@ -201,7 +201,7 @@ func Test_newMultiClusterResourceVersionFromString(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "success",
|
name: "success - normal",
|
||||||
args: args{
|
args: args{
|
||||||
s: base64.RawURLEncoding.EncodeToString([]byte(`{"cluster1":"1","cluster2":"2"}`)),
|
s: base64.RawURLEncoding.EncodeToString([]byte(`{"cluster1":"1","cluster2":"2"}`)),
|
||||||
},
|
},
|
||||||
|
@ -212,6 +212,30 @@ func Test_newMultiClusterResourceVersionFromString(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "success - empty cluster name",
|
||||||
|
args: args{
|
||||||
|
s: base64.RawURLEncoding.EncodeToString([]byte(`{"":"1","cluster2":"2"}`)),
|
||||||
|
},
|
||||||
|
want: &multiClusterResourceVersion{
|
||||||
|
rvs: map[string]string{
|
||||||
|
"": "1",
|
||||||
|
"cluster2": "2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success - empty ResourceVersion",
|
||||||
|
args: args{
|
||||||
|
s: base64.RawURLEncoding.EncodeToString([]byte(`{"cluster1":"","cluster2":""}`)),
|
||||||
|
},
|
||||||
|
want: &multiClusterResourceVersion{
|
||||||
|
rvs: map[string]string{
|
||||||
|
"cluster1": "",
|
||||||
|
"cluster2": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -310,7 +334,7 @@ func Test_multiClusterResourceVersion_String(t *testing.T) {
|
||||||
want: "",
|
want: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "get success",
|
name: "get success - normal",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
rvs: map[string]string{
|
rvs: map[string]string{
|
||||||
"cluster1": "1",
|
"cluster1": "1",
|
||||||
|
@ -319,6 +343,26 @@ func Test_multiClusterResourceVersion_String(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: base64.RawURLEncoding.EncodeToString([]byte(`{"cluster1":"1","cluster2":"2"}`)),
|
want: base64.RawURLEncoding.EncodeToString([]byte(`{"cluster1":"1","cluster2":"2"}`)),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "get success - empty cluster name",
|
||||||
|
fields: fields{
|
||||||
|
rvs: map[string]string{
|
||||||
|
"": "1",
|
||||||
|
"cluster2": "2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: base64.RawURLEncoding.EncodeToString([]byte(`{"":"1","cluster2":"2"}`)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get success - empty ResourceVersion",
|
||||||
|
fields: fields{
|
||||||
|
rvs: map[string]string{
|
||||||
|
"cluster1": "",
|
||||||
|
"cluster2": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: base64.RawURLEncoding.EncodeToString([]byte(`{"cluster1":"","cluster2":""}`)),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue