-
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.
xdsrouting: resolver to generate service config with routes, and pick…
… routing balancer (#3751)
- Loading branch information
Showing
6 changed files
with
965 additions
and
16 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
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,186 @@ | ||
/* | ||
* | ||
* Copyright 2020 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 | ||
* | ||
* 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 resolver | ||
|
||
import ( | ||
"fmt" | ||
"math" | ||
"sort" | ||
"strconv" | ||
|
||
"google.golang.org/grpc/internal/grpcrand" | ||
xdsclient "google.golang.org/grpc/xds/internal/client" | ||
) | ||
|
||
type actionWithAssignedName struct { | ||
// cluster:weight, "A":40, "B":60 | ||
clustersWithWeights map[string]uint32 | ||
// clusterNames, without weights, sorted and hashed, "A_B_" | ||
clusterNames string | ||
// The assigned name, clusters plus a random number, "A_B_1" | ||
assignedName string | ||
// randomNumber is the number appended to assignedName. | ||
randomNumber int64 | ||
} | ||
|
||
// newActionsFromRoutes gets actions from the routes, and turns them into a map | ||
// keyed by the hash of the clusters. | ||
// | ||
// In the returned map, all actions don't have assignedName. The assignedName | ||
// will be filled in after comparing the new actions with the existing actions, | ||
// so when a new and old action only diff in weights, the new action can reuse | ||
// the old action's name. | ||
// | ||
// from | ||
// {B:60, A:40}, {A:30, B:70}, {B:90, C:10} | ||
// | ||
// to | ||
// A40_B60_: {{A:40, B:60}, "A_B_", ""} | ||
// A30_B70_: {{A:30, B:70}, "A_B_", ""} | ||
// B90_C10_: {{B:90, C:10}, "B_C_", ""} | ||
func newActionsFromRoutes(routes []*xdsclient.Route) map[string]actionWithAssignedName { | ||
newActions := make(map[string]actionWithAssignedName) | ||
for _, route := range routes { | ||
var clusterNames []string | ||
for n := range route.Action { | ||
clusterNames = append(clusterNames, n) | ||
} | ||
|
||
// Sort names to be consistent. | ||
sort.Strings(clusterNames) | ||
clustersOnly := "" | ||
clustersWithWeight := "" | ||
for _, c := range clusterNames { | ||
// Generates A_B_ | ||
clustersOnly = clustersOnly + c + "_" | ||
// Generates A40_B60_ | ||
clustersWithWeight = clustersWithWeight + c + strconv.FormatUint(uint64(route.Action[c]), 10) + "_" | ||
} | ||
|
||
if _, ok := newActions[clustersWithWeight]; !ok { | ||
newActions[clustersWithWeight] = actionWithAssignedName{ | ||
clustersWithWeights: route.Action, | ||
clusterNames: clustersOnly, | ||
} | ||
} | ||
} | ||
return newActions | ||
} | ||
|
||
// updateActions takes a new map of actions, and updates the existing action map in the resolver. | ||
// | ||
// In the old map, all actions have assignedName set. | ||
// In the new map, all actions have no assignedName. | ||
// | ||
// After the update, the action map is updated to have all actions from the new | ||
// map, with assignedName: | ||
// - if the new action exists in old, get the old name | ||
// - if the new action doesn't exist in old | ||
// - if there is an old action that will be removed, and has the same set of | ||
// clusters, reuse the old action's name | ||
// - otherwise, generate a new name | ||
func (r *xdsResolver) updateActions(newActions map[string]actionWithAssignedName) { | ||
if r.actions == nil { | ||
r.actions = make(map[string]actionWithAssignedName) | ||
} | ||
|
||
// Delete actions from existingActions if they are not in newActions. Keep | ||
// the removed actions in a map, with key as clusterNames without weights, | ||
// so their assigned names can be reused. | ||
existingActions := r.actions | ||
actionsRemoved := make(map[string][]string) | ||
for actionHash, act := range existingActions { | ||
if _, ok := newActions[actionHash]; !ok { | ||
actionsRemoved[act.clusterNames] = append(actionsRemoved[act.clusterNames], act.assignedName) | ||
delete(existingActions, actionHash) | ||
} | ||
} | ||
|
||
// Find actions in newActions but not in oldActions. Add them, and try to | ||
// reuse assigned names from actionsRemoved. | ||
if r.usedActionNameRandomNumber == nil { | ||
r.usedActionNameRandomNumber = make(map[int64]bool) | ||
} | ||
for actionHash, act := range newActions { | ||
if _, ok := existingActions[actionHash]; !ok { | ||
if assignedNamed, ok := actionsRemoved[act.clusterNames]; ok { | ||
// Reuse the first assigned name from actionsRemoved. | ||
act.assignedName = assignedNamed[0] | ||
// If there are more names to reuse after this, update the slice | ||
// in the map. Otherwise, remove the entry from the map. | ||
if len(assignedNamed) > 1 { | ||
actionsRemoved[act.clusterNames] = assignedNamed[1:] | ||
} else { | ||
delete(actionsRemoved, act.clusterNames) | ||
} | ||
existingActions[actionHash] = act | ||
continue | ||
} | ||
// Generate a new name. | ||
act.randomNumber = r.nextAssignedNameRandomNumber() | ||
act.assignedName = fmt.Sprintf("%s%d", act.clusterNames, act.randomNumber) | ||
existingActions[actionHash] = act | ||
} | ||
} | ||
|
||
// Delete entry from nextIndex if all actions with the clusters are removed. | ||
remainingRandomNumbers := make(map[int64]bool) | ||
for _, act := range existingActions { | ||
remainingRandomNumbers[act.randomNumber] = true | ||
} | ||
r.usedActionNameRandomNumber = remainingRandomNumbers | ||
} | ||
|
||
var grpcrandInt63n = grpcrand.Int63n | ||
|
||
func (r *xdsResolver) nextAssignedNameRandomNumber() int64 { | ||
for { | ||
t := grpcrandInt63n(math.MaxInt32) | ||
if !r.usedActionNameRandomNumber[t] { | ||
return t | ||
} | ||
} | ||
} | ||
|
||
// getActionAssignedName hashes the clusters from the action, and find the | ||
// assigned action name. The assigned action names are kept in r.actions, with | ||
// the clusters name hash as map key. | ||
// | ||
// The assigned action name is not simply the hash. For example, the hash can be | ||
// "A40_B60_", but the assigned name can be "A_B_0". It's this way so the action | ||
// can be reused if only weights are changing. | ||
func (r *xdsResolver) getActionAssignedName(action map[string]uint32) string { | ||
var clusterNames []string | ||
for n := range action { | ||
clusterNames = append(clusterNames, n) | ||
} | ||
// Hash cluster names. Sort names to be consistent. | ||
sort.Strings(clusterNames) | ||
clustersWithWeight := "" | ||
for _, c := range clusterNames { | ||
// Generates hash "A40_B60_". | ||
clustersWithWeight = clustersWithWeight + c + strconv.FormatUint(uint64(action[c]), 10) + "_" | ||
} | ||
// Look in r.actions for the assigned action name. | ||
if act, ok := r.actions[clustersWithWeight]; ok { | ||
return act.assignedName | ||
} | ||
r.logger.Warningf("no assigned name found for action %v", action) | ||
return "" | ||
} |
Oops, something went wrong.