mirror of https://github.com/tikv/pd.git
273 lines
7.6 KiB
Go
273 lines
7.6 KiB
Go
// Copyright 2021 TiKV Project 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 labeler
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/pingcap/failpoint"
|
|
"github.com/pingcap/log"
|
|
|
|
"github.com/tikv/pd/pkg/errs"
|
|
)
|
|
|
|
// RegionLabel is the label of a region.
|
|
// NOTE: This type is exported by HTTP API. Please pay more attention when modifying it.
|
|
type RegionLabel struct {
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
TTL string `json:"ttl,omitempty"`
|
|
StartAt string `json:"start_at,omitempty"`
|
|
expire *time.Time
|
|
}
|
|
|
|
func (l *RegionLabel) String() string {
|
|
return fmt.Sprintf("key: %s, value: %s", l.Key, l.Value)
|
|
}
|
|
|
|
// LabelRule is the rule to assign labels to a region.
|
|
// NOTE: This type is exported by HTTP API. Please pay more attention when modifying it.
|
|
type LabelRule struct {
|
|
ID string `json:"id"`
|
|
Index int `json:"index"`
|
|
Labels []RegionLabel `json:"labels"`
|
|
RuleType string `json:"rule_type"`
|
|
Data any `json:"data"`
|
|
minExpire *time.Time
|
|
}
|
|
|
|
func (rule *LabelRule) String() string {
|
|
var b strings.Builder
|
|
b.WriteString(fmt.Sprintf("id: %s, index: %d, type: %s", rule.ID, rule.Index, rule.RuleType))
|
|
b.WriteString(", labels: ")
|
|
for i, l := range rule.Labels {
|
|
if i == 0 {
|
|
b.WriteString("[")
|
|
}
|
|
b.WriteString(l.String())
|
|
if i == len(rule.Labels)-1 {
|
|
b.WriteString("]")
|
|
} else {
|
|
b.WriteString(", ")
|
|
}
|
|
}
|
|
b.WriteString(", data: ")
|
|
ranges := rule.Data.([]*KeyRangeRule)
|
|
for i, r := range ranges {
|
|
if i == 0 {
|
|
b.WriteString("[")
|
|
}
|
|
b.WriteString(fmt.Sprintf("startKey: {%s}, endKey: {%s}", r.StartKeyHex, r.EndKeyHex))
|
|
if i == len(ranges)-1 {
|
|
b.WriteString("]")
|
|
} else {
|
|
b.WriteString(", ")
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// NewLabelRuleFromJSON creates a label rule from the JSON data.
|
|
func NewLabelRuleFromJSON(data []byte) (*LabelRule, error) {
|
|
lr := &LabelRule{}
|
|
err := json.Unmarshal(data, lr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return lr, nil
|
|
}
|
|
|
|
const (
|
|
// KeyRange is the rule type that specifies a list of key ranges.
|
|
KeyRange = "key-range"
|
|
)
|
|
|
|
const (
|
|
scheduleOptionLabel = "schedule"
|
|
scheduleOptionValueDeny = "deny"
|
|
)
|
|
|
|
// KeyRangeRule contains the start key and end key of the LabelRule.
|
|
// NOTE: This type is exported by HTTP API. Please pay more attention when modifying it.
|
|
type KeyRangeRule struct {
|
|
StartKey []byte `json:"-"` // range start key
|
|
StartKeyHex string `json:"start_key"` // hex format start key, for marshal/unmarshal
|
|
EndKey []byte `json:"-"` // range end key
|
|
EndKeyHex string `json:"end_key"` // hex format end key, for marshal/unmarshal
|
|
}
|
|
|
|
// LabelRulePatch is the patch to update the label rules.
|
|
// NOTE: This type is exported by HTTP API. Please pay more attention when modifying it.
|
|
type LabelRulePatch struct {
|
|
SetRules []*LabelRule `json:"sets"`
|
|
DeleteRules []string `json:"deletes"`
|
|
}
|
|
|
|
func (l *RegionLabel) expireBefore(t time.Time) bool {
|
|
failpoint.Inject("regionLabelExpireSub1Minute", func() {
|
|
if l.expire != nil {
|
|
*l.expire = l.expire.Add(-time.Minute)
|
|
}
|
|
})
|
|
if l.expire == nil {
|
|
return false
|
|
}
|
|
return l.expire.Before(t)
|
|
}
|
|
|
|
func (l *RegionLabel) checkAndAdjustExpire() (err error) {
|
|
if len(l.TTL) == 0 {
|
|
l.expire = nil
|
|
return
|
|
}
|
|
ttl, err := time.ParseDuration(l.TTL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var startAt time.Time
|
|
if len(l.StartAt) == 0 {
|
|
startAt = time.Now()
|
|
l.StartAt = startAt.Format(time.UnixDate)
|
|
} else {
|
|
startAt, err = time.Parse(time.UnixDate, l.StartAt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
expire := startAt.Add(ttl)
|
|
l.expire = &expire
|
|
return nil
|
|
}
|
|
|
|
func (rule *LabelRule) checkAndRemoveExpireLabels(now time.Time) bool {
|
|
labels := make([]RegionLabel, 0)
|
|
rule.minExpire = nil
|
|
for _, l := range rule.Labels {
|
|
if l.expireBefore(now) {
|
|
continue
|
|
}
|
|
labels = append(labels, l)
|
|
if rule.minExpire == nil || l.expireBefore(*rule.minExpire) {
|
|
rule.minExpire = l.expire
|
|
}
|
|
}
|
|
|
|
if len(labels) == len(rule.Labels) {
|
|
return false
|
|
}
|
|
rule.Labels = labels
|
|
return true
|
|
}
|
|
|
|
func (rule *LabelRule) checkAndAdjust() error {
|
|
if rule.ID == "" {
|
|
return errs.ErrRegionRuleContent.FastGenByArgs("empty rule id")
|
|
}
|
|
if len(rule.Labels) == 0 {
|
|
return errs.ErrRegionRuleContent.FastGenByArgs("no region labels")
|
|
}
|
|
for id, l := range rule.Labels {
|
|
if l.Key == "" {
|
|
return errs.ErrRegionRuleContent.FastGenByArgs("empty region label key")
|
|
}
|
|
if l.Value == "" {
|
|
return errs.ErrRegionRuleContent.FastGenByArgs("empty region label value")
|
|
}
|
|
if err := rule.Labels[id].checkAndAdjustExpire(); err != nil {
|
|
err := fmt.Sprintf("region label with invalid ttl info %v", err)
|
|
return errs.ErrRegionRuleContent.FastGenByArgs(err)
|
|
}
|
|
}
|
|
rule.checkAndRemoveExpireLabels(time.Now())
|
|
if len(rule.Labels) == 0 {
|
|
return errs.ErrRegionRuleContent.FastGenByArgs("region label with expired ttl")
|
|
}
|
|
|
|
// TODO: change it to switch statement once we support more types.
|
|
if rule.RuleType == KeyRange {
|
|
var err error
|
|
rule.Data, err = initKeyRangeRulesFromLabelRuleData(rule.Data)
|
|
return err
|
|
}
|
|
log.Error("invalid rule type", zap.String("rule-type", rule.RuleType))
|
|
return errs.ErrRegionRuleContent.FastGenByArgs(fmt.Sprintf("invalid rule type: %s", rule.RuleType))
|
|
}
|
|
|
|
func (rule *LabelRule) expireBefore(t time.Time) bool {
|
|
if rule.minExpire == nil {
|
|
return false
|
|
}
|
|
return rule.minExpire.Before(t)
|
|
}
|
|
|
|
// initKeyRangeRulesFromLabelRuleData init and adjust []KeyRangeRule from `LabelRule.Data`
|
|
func initKeyRangeRulesFromLabelRuleData(data any) ([]*KeyRangeRule, error) {
|
|
rules, ok := data.([]any)
|
|
if !ok {
|
|
return nil, errs.ErrRegionRuleContent.FastGenByArgs(fmt.Sprintf("invalid rule type: %T", data))
|
|
}
|
|
if len(rules) == 0 {
|
|
return nil, errs.ErrRegionRuleContent.FastGenByArgs("no key ranges")
|
|
}
|
|
rs := make([]*KeyRangeRule, 0, len(rules))
|
|
for _, r := range rules {
|
|
rr, err := initAndAdjustKeyRangeRule(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rs = append(rs, rr)
|
|
}
|
|
return rs, nil
|
|
}
|
|
|
|
// initAndAdjustKeyRangeRule inits and adjusts the KeyRangeRule from one item in `LabelRule.Data`
|
|
func initAndAdjustKeyRangeRule(rule any) (*KeyRangeRule, error) {
|
|
data, ok := rule.(map[string]any)
|
|
if !ok {
|
|
return nil, errs.ErrRegionRuleContent.FastGenByArgs(fmt.Sprintf("invalid rule type: %T", reflect.TypeOf(rule)))
|
|
}
|
|
startKey, ok := data["start_key"].(string)
|
|
if !ok {
|
|
return nil, errs.ErrRegionRuleContent.FastGenByArgs(fmt.Sprintf("invalid startKey type: %T", reflect.TypeOf(data["start_key"])))
|
|
}
|
|
endKey, ok := data["end_key"].(string)
|
|
if !ok {
|
|
return nil, errs.ErrRegionRuleContent.FastGenByArgs(fmt.Sprintf("invalid endKey type: %T", reflect.TypeOf(data["end_key"])))
|
|
}
|
|
var r KeyRangeRule
|
|
r.StartKeyHex, r.EndKeyHex = startKey, endKey
|
|
var err error
|
|
r.StartKey, err = hex.DecodeString(r.StartKeyHex)
|
|
if err != nil {
|
|
return nil, errs.ErrHexDecodingString.FastGenByArgs(r.StartKeyHex)
|
|
}
|
|
r.EndKey, err = hex.DecodeString(r.EndKeyHex)
|
|
if err != nil {
|
|
return nil, errs.ErrHexDecodingString.FastGenByArgs(r.EndKeyHex)
|
|
}
|
|
if len(r.EndKey) > 0 && bytes.Compare(r.EndKey, r.StartKey) <= 0 {
|
|
return nil, errs.ErrRegionRuleContent.FastGenByArgs("endKey should be greater than startKey")
|
|
}
|
|
return &r, nil
|
|
}
|