mirror of https://github.com/grpc/grpc-go.git
187 lines
6.2 KiB
Go
187 lines
6.2 KiB
Go
/*
|
|
*
|
|
* 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 ""
|
|
}
|