swim/labels.go (100 lines of code) (raw):

package swim import ( "errors" "strings" "github.com/uber/ringpop-go/util" ) var ( // DefaultLabelOptions contain the default values to be used to limit the // amount of data being gossipped over the network. The defaults have been // chosen with the following assumptions. // 1. 1000 node cluster // 2. Every byte available is used (yes, characters are not bytes but these // are ballpark figures to protect developers) // 3. Worst case would have continues fullsync's, meaning the complete // memberlist being sent over the wire 5 times a second. // When all contiditions are met the Labels would add the following load // (non-compressed) to the network: // (32+128)*5*1000*5*8 = ~32mbit/s DefaultLabelOptions = LabelOptions{ KeySize: 32, ValueSize: 128, Count: 5, } // ErrLabelSizeExceeded indicates that an operation on labels would exceed // the configured size limits on labels. This is to prevent applications // bloating the gossip protocol with too much data. Ringpop can be // configured with the settings to control the amount of data that can be // put in a nodes labels. ErrLabelSizeExceeded = errors.New("label operation exceeds configured label limits") // ErrLabelInternalKey is an error that is returned when an application // tries to set a label in the internal namespace that is used by ringpop ErrLabelInternalKey = errors.New("label can't be altered by application because it is in the internal ringpop namespace") labelsInternalNamespacePrefix = "__" ) // LabelOptions controlls the limits on labels. Since labels are gossiped on // every ping/ping-req/fullsync we need to limit the amount of data an // application stores in their labels. When needed the defaults can be // overwritten during the construction of ringpop. This should be done with care // to not overwhelm the network with data. type LabelOptions struct { // KeySize is the length a key may use at most KeySize int // ValueSize is the length a value may use at most ValueSize int // Count is the number of maximum allowed (public) labels on a node Count int } func mergeLabelOptions(opts LabelOptions, def LabelOptions) LabelOptions { return LabelOptions{ KeySize: util.SelectInt(opts.KeySize, def.KeySize), ValueSize: util.SelectInt(opts.ValueSize, def.ValueSize), Count: util.SelectInt(opts.Count, def.Count), } } func (lo LabelOptions) validateLabel(current map[string]string, key, value string) error { if isInternalLabel(key) { // we ignore internal labels in the limits return nil } if len(key) > lo.KeySize || len(value) > lo.ValueSize { // if the either the key or the value of the label is bigger then // the max allowed size an error is returned return ErrLabelSizeExceeded } // keep track of all labels that are new in this operation to calculate if // we are exceeding the maximum allowed number of labels additionalLabelCount := 0 _, has := current[key] if !has { // only add a count to the countAfter if the key we are looking // at is a new key additionalLabelCount++ } // get the count of the current labels, internal labels will not be counted currentCount := countNonInternalLabels(current) if additionalLabelCount > 0 && (currentCount+additionalLabelCount) > lo.Count { // Only when we add additional labels we check if the new count // (exluding internal labels) will exceed the amount of labels that is // configured. If that is the case we will return an error return ErrLabelSizeExceeded } // all is ok return nil } func (lo LabelOptions) validateLabels(current map[string]string, additional map[string]string) error { // keep track of all labels that are new in this operation to calculate if // we are exceeding the maximum allowed number of labels additionalLabelCount := 0 for key, value := range additional { if isInternalLabel(key) { // we ignore internal labels in the limits continue } if len(key) > lo.KeySize || len(value) > lo.ValueSize { // if the either the key or the value of the label is bigger then // the max allowed size an error is returned return ErrLabelSizeExceeded } _, has := current[key] if !has { // only add a count to the countAfter if the key we are looking // at is a new key additionalLabelCount++ } } // get the count of the current labels, internal labels will not be counted currentCount := countNonInternalLabels(current) if additionalLabelCount > 0 && (currentCount+additionalLabelCount) > lo.Count { // Only when we add additional labels we check if the new count // (exluding internal labels) will exceed the amount of labels that is // configured. If that is the case we will return an error return ErrLabelSizeExceeded } // all is ok return nil } func isInternalLabel(key string) bool { return strings.HasPrefix(key, labelsInternalNamespacePrefix) } func countNonInternalLabels(labels map[string]string) int { count := 0 for key := range labels { if isInternalLabel(key) { continue } count++ } return count } // NodeLabels implements the ringpop.Labels interface and proxies the calls to // the swim.Node backing the membership protocol. type NodeLabels struct { node *Node } // Get the value of a label for this node func (n *NodeLabels) Get(key string) (value string, has bool) { return n.node.memberlist.GetLocalLabel(key) } // Set the key to a specific value. Returning an error when it failed eg. when // the storage capacity for labels has exceed the maximum ammount. (Currently // the storage limit is not implemented) func (n *NodeLabels) Set(key, value string) error { if isInternalLabel(key) { return ErrLabelInternalKey } return n.node.memberlist.SetLocalLabel(key, value) } // Remove a key from the labels func (n *NodeLabels) Remove(key string) (removed bool, err error) { if isInternalLabel(key) { return false, ErrLabelInternalKey } return n.node.memberlist.RemoveLocalLabels(key), nil } // AsMap gets a readonly copy of all the labels assigned to Node. Changes to the // map will not be refelected in the node. func (n *NodeLabels) AsMap() map[string]string { return n.node.memberlist.LocalLabelsAsMap() }