Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xds: Add RLS Cluster Specifier Plugin #5004

Merged
merged 5 commits into from Dec 15, 2021
Merged

Conversation

zasweq
Copy link
Contributor

@zasweq zasweq commented Nov 24, 2021

This PR adds the RLS Cluster Specifier Plugin. This is branched off #4987. The RLS Cluster Specifier Plugin will not be functional until the RLS LB Policy itself gets completed, as right now the call into the RLS LB Policies ParseConfig() for validation purposes is commented out.

RELEASE NOTES: None

// LBConfigJSON is the RLS LB Policies configuration in JSON format.
// RouteLookupConfig will be a raw JSON string from the passed in proto
// configuration, and the other fields will be hardcoded.
type LBConfigJSON struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type shouldn't need to be exported.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched. This was exported only for testing, now that we moved testing to same package I can unexport.

// RouteLookupConfig will be a raw JSON string from the passed in proto
// configuration, and the other fields will be hardcoded.
type LBConfigJSON struct {
RouteLookupConfig interface{} `json:"routeLookupConfig"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[]byte?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to json.RawMessage (an alias for []byte).

if err := ptypes.UnmarshalAny(any, rlcs); err != nil {
return nil, fmt.Errorf("rls_csp: error parsing config %v: %v", cfg, err)
}
// Do we use protojson or regular marshal function - see if Marshal produces correct results?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wouldn't we use protojson? Even if json.Marshal happens to work given what's currently there, protojson is what you're supposed to use to marshal proto into JSON and vice-versa.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched. I think I could only find two instances throughout the codebase of using protojson, which is why I was a touch hesitant. Even ParseConfig for the RLS LB policy itself (json raw bytestring -> RouteLookupConfig proto) in master doesn't use protojson: https://github.com/grpc/grpc-go/blob/master/balancer/rls/internal/config.go#L132.

// return nil, fmt.Errorf("error: %v", err)
// }

return []map[string]interface{}{{"rls_experimental": lbCfgJSON}}, nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return clusterspecifier.BalancerConfig{{"rls_experimental": lbCfgJSON}}, nil

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched.

Comment on lines 82 to 92
// Will be uncommented once RLS LB Policy completed

// rawJSON, err := json.Marshal(lbCfgJSON)
// if err != nil {
// return nil, fmt.Errorf("rls_csp: error marshaling load balancing config %v: %v", lbCfgJSON, err)
// }

// _, err := rls.ParseConfig(rawJSON) (will this function need to change its logic at all?)
// if err != nil {
// return nil, fmt.Errorf("error: %v", err)
// }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rls.ParseConfig exists, actually. Can we make it accessible and use it?

Also, it would be used via:

rlsBB := balancer.Get("experimental_rls")
if rlsBB == nil {
	return nil, fmt.Errorf("RLS LB policy not registered")
}
rlsBB.(balancer.ConfigParser).ParseConfig(.....)

Copy link
Contributor Author

@zasweq zasweq Nov 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

RouteLookupConfig: rlcJSON, // "JSON form of RouteLookupClusterSpecifier.config" - RLS in xDS Design Doc
ChildPolicy: []map[string]interface{}{
{
"cds_experimental": struct{}{}, // Is this right? {} in design doc
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does nil work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I marshaled {} into a json.RawMessage to pass RLS Config Parsing.

Comment on lines 762 to 769
var rlsClusterSpecifierConfig = testutils.MarshalAny(&grpc_lookup_v1.RouteLookupClusterSpecifier{
// Once Easwar merges the RLS LB implementation and the rls csp actually
// calls into it for validation, this config will have to be scaled up to
// pass the validations present in the RLS LB implementation.
RouteLookupConfig: &grpc_lookup_v1.RouteLookupConfig{
LookupService: "rls-specifier",
},
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is the right way to test this.

The xdsclient tests shouldn't rely on any specific plugins, and the tests for any specific plugins should not be done in the xdsclient tests. Instead, the rls plugin's tests should be done in xds/internal/clusterspecifier/rls.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmmmm ok noted. Switched to package, and tested input/output of ParseClusterSpecifierConfig().

Copy link
Contributor Author

@zasweq zasweq Dec 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, manually testing and comparing the output (containing long marshaled json.RawMessage) understandably led to some flakiness. I pushed the fix in a separate commit, that way you can see what I had previously, and to see if you'd like me to go back to that and figure out an unflaky way to test the raw JSON string output (the problem was marshaling the proto would sometimes pop out spaces in between fields, sometimes not).

@dfawley dfawley assigned zasweq and unassigned dfawley Nov 24, 2021
Copy link
Contributor Author

@zasweq zasweq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got to all your comments, but commit is still a WIP.

// return nil, fmt.Errorf("error: %v", err)
// }

return []map[string]interface{}{{"rls_experimental": lbCfgJSON}}, nil
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched.

if err := ptypes.UnmarshalAny(any, rlcs); err != nil {
return nil, fmt.Errorf("rls_csp: error parsing config %v: %v", cfg, err)
}
// Do we use protojson or regular marshal function - see if Marshal produces correct results?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched. I think I could only find two instances throughout the codebase of using protojson, which is why I was a touch hesitant. Even ParseConfig for the RLS LB policy itself (json raw bytestring -> RouteLookupConfig proto) in master doesn't use protojson: https://github.com/grpc/grpc-go/blob/master/balancer/rls/internal/config.go#L132.

// RouteLookupConfig will be a raw JSON string from the passed in proto
// configuration, and the other fields will be hardcoded.
type LBConfigJSON struct {
RouteLookupConfig interface{} `json:"routeLookupConfig"`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to json.RawMessage (an alias for []byte).

// LBConfigJSON is the RLS LB Policies configuration in JSON format.
// RouteLookupConfig will be a raw JSON string from the passed in proto
// configuration, and the other fields will be hardcoded.
type LBConfigJSON struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched. This was exported only for testing, now that we moved testing to same package I can unexport.

Comment on lines 762 to 769
var rlsClusterSpecifierConfig = testutils.MarshalAny(&grpc_lookup_v1.RouteLookupClusterSpecifier{
// Once Easwar merges the RLS LB implementation and the rls csp actually
// calls into it for validation, this config will have to be scaled up to
// pass the validations present in the RLS LB implementation.
RouteLookupConfig: &grpc_lookup_v1.RouteLookupConfig{
LookupService: "rls-specifier",
},
})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmmmm ok noted. Switched to package, and tested input/output of ParseClusterSpecifierConfig().

Comment on lines 82 to 92
// Will be uncommented once RLS LB Policy completed

// rawJSON, err := json.Marshal(lbCfgJSON)
// if err != nil {
// return nil, fmt.Errorf("rls_csp: error marshaling load balancing config %v: %v", lbCfgJSON, err)
// }

// _, err := rls.ParseConfig(rawJSON) (will this function need to change its logic at all?)
// if err != nil {
// return nil, fmt.Errorf("error: %v", err)
// }
Copy link
Contributor Author

@zasweq zasweq Nov 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@zasweq zasweq assigned dfawley and unassigned zasweq Dec 1, 2021
@dfawley dfawley modified the milestones: 1.43 release, 1.44 Release Dec 6, 2021
return nil, fmt.Errorf("rls_csp: validation error from rls lb policy parsing %v", err)
}

return clusterspecifier.BalancerConfig{{"rls_experimental": lbCfgJSON}}, nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to suggest using an exported constant in the RLS package, but I don't think we can do that for visibility reasons without moving stuff around invasively.

Will the tests break when there is no "rls_experimental" LB policy anymore (it will eventually be renamed to "rls")? If so, then I think this is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is named rls in master. I'm going to switch what gets emitted out of this function to rls: config, that way once it gets to balancer group, it will successfully do balancer.Get("rls").

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, please rename to rls_experimental throughout.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched.

*/

// Package rls implements the RLS cluster specifier plugin.
package rls
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be importing the RLS package? Seems like it should.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added import of the RLS package.

)

func init() {
clusterspecifier.Register(rls{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be conditional on GRPC_EXPERIMENTAL_XDS_RLS_LB?

Copy link
Contributor Author

@zasweq zasweq Dec 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

if err != nil {
return nil, fmt.Errorf("rls_csp: error marshaling route lookup config: %v: %v", rlcs.GetRouteLookupConfig(), err)
}
emptyJSON, err := json.Marshal(struct{}{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this just json.RawMessage("{}")? If so, let's use that (inline below) instead of something that can error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched.

return nil, fmt.Errorf("rls_csp: error marshaling load balancing config %v: %v", lbCfgJSON, err)
}

rlsBB := balancer.Get("rls")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do this as a global?

Also...shouldn't this match "rls_experimental" below? Can you use a single const for both of these?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the name it's registered by in balancer/rls/internal. Below corresponds to what is defined in design doc. Switched the below config to "rls", as that is what is currently in master, so used one const for both.

if cs == nil {
t.Fatal("Error getting cluster specifier")
}
_, err := cs.ParseClusterSpecifierConfig(test.rlcs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validate the result? (I'm assuming it would be marshaling the result to JSON and compare to a string.)

Copy link
Contributor Author

@zasweq zasweq Dec 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, with a solution discussed offline.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was expecting to see something like this here now:

lbCfgJSON, err := json.Marshal(lbCfg)
// handle err
var got interface{}
err := json.Unmarshal(lbCfgJSON, &got)
// handle err
wantJSON := `
{
	// JSON that we wanted
}
`
var want interface{}
err := json.Unmarshal(wantJSON, &want)
// handle err
if diff := cmp.Diff(got, want); diff != "" {
	// error
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Although I kept the want as a struct, and then Marshaled and then Unmarshaled. I think it's cleaner.

@dfawley dfawley assigned zasweq and unassigned dfawley Dec 7, 2021
@zasweq zasweq assigned dfawley and unassigned zasweq Dec 9, 2021
Copy link
Contributor Author

@zasweq zasweq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, these were sitting in pending.

*/

// Package rls implements the RLS cluster specifier plugin.
package rls
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added import of the RLS package.

)

func init() {
clusterspecifier.Register(rls{})
Copy link
Contributor Author

@zasweq zasweq Dec 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

if err != nil {
return nil, fmt.Errorf("rls_csp: error marshaling route lookup config: %v: %v", rlcs.GetRouteLookupConfig(), err)
}
emptyJSON, err := json.Marshal(struct{}{})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched.

return nil, fmt.Errorf("rls_csp: error marshaling load balancing config %v: %v", lbCfgJSON, err)
}

rlsBB := balancer.Get("rls")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the name it's registered by in balancer/rls/internal. Below corresponds to what is defined in design doc. Switched the below config to "rls", as that is what is currently in master, so used one const for both.

return nil, fmt.Errorf("rls_csp: validation error from rls lb policy parsing %v", err)
}

return clusterspecifier.BalancerConfig{{"rls_experimental": lbCfgJSON}}, nil
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is named rls in master. I'm going to switch what gets emitted out of this function to rls: config, that way once it gets to balancer group, it will successfully do balancer.Get("rls").

if cs == nil {
t.Fatal("Error getting cluster specifier")
}
_, err := cs.ParseClusterSpecifierConfig(test.rlcs)
Copy link
Contributor Author

@zasweq zasweq Dec 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, with a solution discussed offline.

@dfawley dfawley assigned zasweq and unassigned dfawley Dec 14, 2021
@zasweq zasweq assigned dfawley and unassigned zasweq Dec 15, 2021
@@ -24,7 +24,7 @@ import (
"google.golang.org/grpc/internal/grpcsync"
)

const rlsBalancerName = "rls"
const rlsBalancerName = "rls_experimental"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this was changed on master; hopefully rebasing will go smoothly.

Comment on lines 102 to 104
if !cmp.Equal(want, got, cmpopts.EquateEmpty()) {
t.Fatalf("ParseClusterSpecifierConfig(%+v) returned expected, diff (-want +got):\\n%s", test.rlcs, cmp.Diff(want, got, cmpopts.EquateEmpty()))
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use if diff := cmp.Diff(want, got, cmpopts.EquateEmpty()); diff != "" { and then there's no need to call Diff in the error message.

@dfawley dfawley assigned zasweq and unassigned dfawley Dec 15, 2021
@zasweq zasweq merged commit 029b822 into grpc:master Dec 15, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 14, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants