-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
xds/federation: resource name parsing (#4991)
- Loading branch information
Showing
2 changed files
with
234 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* | ||
* | ||
* Copyright 2021 gRPC authors. | ||
* | ||
* 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 | ||
* | ||
* https://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 xdsresource | ||
|
||
import ( | ||
"net/url" | ||
"sort" | ||
"strings" | ||
|
||
"google.golang.org/grpc/internal/envconfig" | ||
) | ||
|
||
// Name contains the parsed component of an xDS resource name. | ||
// | ||
// An xDS resource name is in the format of | ||
// xdstp:https://[{authority}]/{resource type}/{id/*}?{context parameters}{#processing directive,*} | ||
// | ||
// See | ||
// https://github.com/cncf/xds/blob/main/proposals/TP1-xds-transport-next.md#uri-based-xds-resource-names | ||
// for details, and examples. | ||
type Name struct { | ||
Scheme string | ||
Authority string | ||
Type string | ||
ID string | ||
|
||
ContextParams map[string]string | ||
|
||
processingDirective string | ||
} | ||
|
||
// ParseName splits the name and returns a struct representation of the Name. | ||
// | ||
// If the name isn't a valid new-style xDS name, field ID is set to the input. | ||
// Note that this is not an error, because we still support the old-style | ||
// resource names (those not starting with "xdstp:"). | ||
// | ||
// The caller can tell if the parsing is successful by checking the returned | ||
// Scheme. | ||
func ParseName(name string) *Name { | ||
if !envconfig.XDSFederation { | ||
// Return "" scheme to use the default authority for the server. | ||
return &Name{ID: name} | ||
} | ||
if !strings.Contains(name, ":https://") { | ||
// Only the long form URL, with :https://, is valid. | ||
return &Name{ID: name} | ||
} | ||
parsed, err := url.Parse(name) | ||
if err != nil { | ||
return &Name{ID: name} | ||
} | ||
|
||
ret := &Name{ | ||
Scheme: parsed.Scheme, | ||
Authority: parsed.Host, | ||
} | ||
split := strings.SplitN(parsed.Path, "/", 3) | ||
if len(split) < 3 { | ||
// Path is in the format of "/type/id". There must be at least 3 | ||
// segments after splitting. | ||
return &Name{ID: name} | ||
} | ||
ret.Type = split[1] | ||
ret.ID = split[2] | ||
if len(parsed.Query()) != 0 { | ||
ret.ContextParams = make(map[string]string) | ||
for k, vs := range parsed.Query() { | ||
if len(vs) > 0 { | ||
// We only keep one value of each key. Behavior for multiple values | ||
// is undefined. | ||
ret.ContextParams[k] = vs[0] | ||
} | ||
} | ||
} | ||
// TODO: processing directive (the part comes after "#" in the URL, stored | ||
// in parsed.RawFragment) is kept but not processed. Add support for that | ||
// when it's needed. | ||
ret.processingDirective = parsed.RawFragment | ||
return ret | ||
} | ||
|
||
// String returns a canonicalized string of name. The context parameters are | ||
// sorted by the keys. | ||
func (n *Name) String() string { | ||
if n.Scheme == "" { | ||
return n.ID | ||
} | ||
|
||
// Sort and build query. | ||
keys := make([]string, 0, len(n.ContextParams)) | ||
for k := range n.ContextParams { | ||
keys = append(keys, k) | ||
} | ||
sort.Strings(keys) | ||
var pairs []string | ||
for _, k := range keys { | ||
pairs = append(pairs, strings.Join([]string{k, n.ContextParams[k]}, "=")) | ||
} | ||
rawQuery := strings.Join(pairs, "&") | ||
|
||
path := n.Type | ||
if n.ID != "" { | ||
path = path + "/" + n.ID | ||
} | ||
|
||
tempURL := &url.URL{ | ||
Scheme: n.Scheme, | ||
Host: n.Authority, | ||
Path: path, | ||
RawQuery: rawQuery, | ||
RawFragment: n.processingDirective, | ||
} | ||
return tempURL.String() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* | ||
* | ||
* Copyright 2021 gRPC authors. | ||
* | ||
* 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 | ||
* | ||
* https://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 xdsresource | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"google.golang.org/grpc/internal/envconfig" | ||
) | ||
|
||
func TestParseName(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
env bool // Whether federation env is set to true. | ||
in string | ||
want *Name | ||
}{ | ||
{ | ||
name: "env off", | ||
env: false, | ||
in: "xdstp:https://auth/type/id", | ||
want: &Name{ID: "xdstp:https://auth/type/id"}, | ||
}, | ||
{ | ||
name: "old style name", | ||
env: true, | ||
in: "test-resource", | ||
want: &Name{ID: "test-resource"}, | ||
}, | ||
{ | ||
name: "invalid not url", | ||
env: true, | ||
in: "a:/b/c", | ||
want: &Name{ID: "a:/b/c"}, | ||
}, | ||
{ | ||
name: "invalid no resource type", | ||
env: true, | ||
in: "xdstp:https://auth/id", | ||
want: &Name{ID: "xdstp:https://auth/id"}, | ||
}, | ||
{ | ||
name: "valid no ctx params", | ||
env: true, | ||
in: "xdstp:https://auth/type/id", | ||
want: &Name{Scheme: "xdstp", Authority: "auth", Type: "type", ID: "id"}, | ||
}, | ||
{ | ||
name: "valid with ctx params", | ||
env: true, | ||
in: "xdstp:https://auth/type/id?a=1&b=2", | ||
want: &Name{Scheme: "xdstp", Authority: "auth", Type: "type", ID: "id", ContextParams: map[string]string{"a": "1", "b": "2"}}, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if tt.env { | ||
defer func() func() { | ||
oldEnv := envconfig.XDSFederation | ||
envconfig.XDSFederation = true | ||
return func() { envconfig.XDSFederation = oldEnv } | ||
}()() | ||
} | ||
if got := ParseName(tt.in); !reflect.DeepEqual(got, tt.want) { | ||
t.Errorf("ParseName() = %#v, want %#v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
// TestNameStringCtxParamsOrder covers the case that if two names differ only in | ||
// context parameter __order__, the parsed name.String() has the same value. | ||
func TestNameStringCtxParamsOrder(t *testing.T) { | ||
oldEnv := envconfig.XDSFederation | ||
envconfig.XDSFederation = true | ||
defer func() { envconfig.XDSFederation = oldEnv }() | ||
|
||
const ( | ||
a = "xdstp:https://auth/type/id?a=1&b=2" | ||
b = "xdstp:https://auth/type/id?b=2&a=1" | ||
) | ||
aParsed := ParseName(a).String() | ||
bParsed := ParseName(b).String() | ||
|
||
if aParsed != bParsed { | ||
t.Fatalf("aParsed.String() = %q, bParsed.String() = %q, want them to be the same", aParsed, bParsed) | ||
} | ||
} |