Files
tidb/ddl/placement/bundle_test.go
2021-06-11 10:44:34 +08:00

379 lines
10 KiB
Go

// Copyright 2021 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.
package placement
import (
"encoding/hex"
"errors"
. "github.com/pingcap/check"
"github.com/pingcap/failpoint"
"github.com/pingcap/parser/ast"
"github.com/pingcap/tidb/tablecodec"
"github.com/pingcap/tidb/util/codec"
)
var _ = Suite(&testBundleSuite{})
type testBundleSuite struct{}
func (s *testBundleSuite) TestEmpty(c *C) {
bundle := &Bundle{ID: GroupID(1)}
c.Assert(bundle.IsEmpty(), IsTrue)
bundle = &Bundle{ID: GroupID(1), Index: 1}
c.Assert(bundle.IsEmpty(), IsFalse)
bundle = &Bundle{ID: GroupID(1), Override: true}
c.Assert(bundle.IsEmpty(), IsFalse)
bundle = &Bundle{ID: GroupID(1), Rules: []*Rule{{ID: "434"}}}
c.Assert(bundle.IsEmpty(), IsFalse)
bundle = &Bundle{ID: GroupID(1), Index: 1, Override: true}
c.Assert(bundle.IsEmpty(), IsFalse)
}
func (s *testBundleSuite) TestClone(c *C) {
bundle := &Bundle{ID: GroupID(1), Rules: []*Rule{{ID: "434"}}}
newBundle := bundle.Clone()
newBundle.ID = GroupID(2)
newBundle.Rules[0] = &Rule{ID: "121"}
c.Assert(bundle, DeepEquals, &Bundle{ID: GroupID(1), Rules: []*Rule{{ID: "434"}}})
c.Assert(newBundle, DeepEquals, &Bundle{ID: GroupID(2), Rules: []*Rule{{ID: "121"}}})
}
func (s *testBundleSuite) TestApplyPlacmentSpec(c *C) {
type TestCase struct {
name string
input []*ast.PlacementSpec
output []*Rule
err error
}
var tests []TestCase
tests = append(tests, TestCase{
name: "empty",
input: []*ast.PlacementSpec{},
output: []*Rule{},
})
rules, err := NewRules(3, `["+zone=sh", "+zone=sh"]`)
c.Assert(err, IsNil)
c.Assert(rules, HasLen, 1)
rules[0].Role = Voter
tests = append(tests, TestCase{
name: "add voter array",
input: []*ast.PlacementSpec{{
Role: ast.PlacementRoleVoter,
Tp: ast.PlacementAdd,
Replicas: 3,
Constraints: `["+zone=sh", "+zone=sh"]`,
}},
output: rules,
})
rules, err = NewRules(3, `["+zone=sh", "+zone=sh"]`)
c.Assert(err, IsNil)
c.Assert(rules, HasLen, 1)
rules[0].Role = Learner
tests = append(tests, TestCase{
name: "add learner array",
input: []*ast.PlacementSpec{{
Role: ast.PlacementRoleLearner,
Tp: ast.PlacementAdd,
Replicas: 3,
Constraints: `["+zone=sh", "+zone=sh"]`,
}},
output: rules,
})
rules, err = NewRules(3, `["+zone=sh", "+zone=sh"]`)
c.Assert(err, IsNil)
c.Assert(rules, HasLen, 1)
rules[0].Role = Follower
tests = append(tests, TestCase{
name: "add follower array",
input: []*ast.PlacementSpec{{
Role: ast.PlacementRoleFollower,
Tp: ast.PlacementAdd,
Replicas: 3,
Constraints: `["+zone=sh", "+zone=sh"]`,
}},
output: rules,
})
tests = append(tests, TestCase{
name: "add invalid constraints",
input: []*ast.PlacementSpec{{
Role: ast.PlacementRoleVoter,
Tp: ast.PlacementAdd,
Replicas: 3,
Constraints: "ne",
}},
err: ErrInvalidConstraintsFormat,
})
tests = append(tests, TestCase{
name: "add empty role",
input: []*ast.PlacementSpec{{
Tp: ast.PlacementAdd,
Replicas: 3,
Constraints: "",
}},
err: ErrMissingRoleField,
})
tests = append(tests, TestCase{
name: "add multiple leaders",
input: []*ast.PlacementSpec{{
Role: ast.PlacementRoleLeader,
Tp: ast.PlacementAdd,
Replicas: 3,
Constraints: "",
}},
err: ErrLeaderReplicasMustOne,
})
rules, err = NewRules(1, "")
c.Assert(err, IsNil)
c.Assert(rules, HasLen, 1)
rules[0].Role = Leader
tests = append(tests, TestCase{
name: "omit leader field",
input: []*ast.PlacementSpec{{
Role: ast.PlacementRoleLeader,
Tp: ast.PlacementAdd,
Constraints: "",
}},
output: rules,
})
rules, err = NewRules(3, `["-zone=sh","+zone=bj"]`)
c.Assert(err, IsNil)
c.Assert(rules, HasLen, 1)
rules[0].Role = Follower
tests = append(tests, TestCase{
name: "drop",
input: []*ast.PlacementSpec{
{
Role: ast.PlacementRoleFollower,
Tp: ast.PlacementAdd,
Replicas: 3,
Constraints: `["- zone=sh", "+zone = bj"]`,
},
{
Role: ast.PlacementRoleVoter,
Tp: ast.PlacementAdd,
Replicas: 3,
Constraints: `["+ zone=sh", "-zone = bj"]`,
},
{
Role: ast.PlacementRoleVoter,
Tp: ast.PlacementDrop,
},
},
output: rules,
})
tests = append(tests, TestCase{
name: "drop unexisted",
input: []*ast.PlacementSpec{{
Role: ast.PlacementRoleLeader,
Tp: ast.PlacementDrop,
Constraints: "",
}},
err: ErrNoRulesToDrop,
})
rules1, err := NewRules(3, `["-zone=sh","+zone=bj"]`)
c.Assert(err, IsNil)
c.Assert(rules1, HasLen, 1)
rules1[0].Role = Follower
rules2, err := NewRules(3, `["+zone=sh","-zone=bj"]`)
c.Assert(err, IsNil)
c.Assert(rules2, HasLen, 1)
rules2[0].Role = Voter
tests = append(tests, TestCase{
name: "alter",
input: []*ast.PlacementSpec{
{
Role: ast.PlacementRoleFollower,
Tp: ast.PlacementAdd,
Replicas: 3,
Constraints: `["- zone=sh", "+zone = bj"]`,
},
{
Role: ast.PlacementRoleVoter,
Tp: ast.PlacementAdd,
Replicas: 3,
Constraints: `["- zone=sh", "+zone = bj"]`,
},
{
Role: ast.PlacementRoleVoter,
Tp: ast.PlacementAlter,
Replicas: 3,
Constraints: `["+ zone=sh", "-zone = bj"]`,
},
},
output: append(rules1, rules2...),
})
for _, t := range tests {
comment := Commentf("%s", t.name)
bundle := &Bundle{}
err := bundle.ApplyPlacementSpec(t.input)
if t.err == nil {
c.Assert(err, IsNil)
matchRules(t.output, bundle.Rules, comment.CheckCommentString(), c)
} else {
c.Assert(errors.Is(err, t.err), IsTrue, comment)
}
}
}
func (s *testBundleSuite) TestString(c *C) {
bundle := &Bundle{
ID: GroupID(1),
}
rules1, err := NewRules(3, `["+zone=sh", "+zone=sh"]`)
c.Assert(err, IsNil)
rules2, err := NewRules(4, `["-zone=sh", "+zone=bj"]`)
c.Assert(err, IsNil)
bundle.Rules = append(rules1, rules2...)
c.Assert(bundle.String(), Equals, `{"group_id":"TiDB_DDL_1","group_index":0,"group_override":false,"rules":[{"group_id":"","id":"","start_key":"","end_key":"","role":"","count":3,"label_constraints":[{"key":"zone","op":"in","values":["sh"]}]},{"group_id":"","id":"","start_key":"","end_key":"","role":"","count":4,"label_constraints":[{"key":"zone","op":"notIn","values":["sh"]},{"key":"zone","op":"in","values":["bj"]}]}]}`)
c.Assert(failpoint.Enable("github.com/pingcap/tidb/ddl/placement/MockMarshalFailure", `return(true)`), IsNil)
defer func() {
c.Assert(failpoint.Disable("github.com/pingcap/tidb/ddl/placement/MockMarshalFailure"), IsNil)
}()
c.Assert(bundle.String(), Equals, "")
}
func (s *testBundleSuite) TestNew(c *C) {
c.Assert(NewBundle(3), DeepEquals, &Bundle{ID: GroupID(3)})
c.Assert(NewBundle(-1), DeepEquals, &Bundle{ID: GroupID(-1)})
}
func (s *testBundleSuite) TestReset(c *C) {
bundle := &Bundle{
ID: GroupID(1),
}
rules, err := NewRules(3, `["+zone=sh", "+zone=sh"]`)
c.Assert(err, IsNil)
bundle.Rules = rules
bundle.Reset(3)
c.Assert(bundle.ID, Equals, GroupID(3))
c.Assert(bundle.Rules, HasLen, 1)
c.Assert(bundle.Rules[0].GroupID, Equals, bundle.ID)
startKey := hex.EncodeToString(codec.EncodeBytes(nil, tablecodec.GenTableRecordPrefix(3)))
c.Assert(bundle.Rules[0].StartKeyHex, Equals, startKey)
endKey := hex.EncodeToString(codec.EncodeBytes(nil, tablecodec.GenTableRecordPrefix(4)))
c.Assert(bundle.Rules[0].EndKeyHex, Equals, endKey)
}
func (s *testBundleSuite) TestTidy(c *C) {
bundle := &Bundle{
ID: GroupID(1),
}
rules0, err := NewRules(1, `["+zone=sh", "+zone=sh"]`)
c.Assert(err, IsNil)
c.Assert(rules0, HasLen, 1)
rules0[0].Count = 0
rules1, err := NewRules(4, `["-zone=sh", "+zone=bj"]`)
c.Assert(err, IsNil)
c.Assert(rules1, HasLen, 1)
rules2, err := NewRules(4, `["-zone=sh", "+zone=bj"]`)
c.Assert(err, IsNil)
bundle.Rules = append(bundle.Rules, rules0...)
bundle.Rules = append(bundle.Rules, rules1...)
bundle.Rules = append(bundle.Rules, rules2...)
err = bundle.Tidy()
c.Assert(err, IsNil)
c.Assert(bundle.Rules, HasLen, 2)
c.Assert(bundle.Rules[0].ID, Equals, "1")
c.Assert(bundle.Rules[0].Constraints, HasLen, 3)
c.Assert(bundle.Rules[0].Constraints[2], DeepEquals, Constraint{
Op: NotIn,
Key: EngineLabelKey,
Values: []string{EngineLabelTiFlash},
})
c.Assert(bundle.Rules[1].ID, Equals, "2")
// merge
rules3, err := NewRules(4, "")
c.Assert(err, IsNil)
c.Assert(rules3, HasLen, 1)
rules3[0].Role = Follower
rules4, err := NewRules(5, "")
c.Assert(err, IsNil)
c.Assert(rules4, HasLen, 1)
rules4[0].Role = Follower
rules0[0].Role = Voter
bundle.Rules = append(bundle.Rules, rules0...)
bundle.Rules = append(bundle.Rules, rules3...)
bundle.Rules = append(bundle.Rules, rules4...)
chkfunc := func() {
c.Assert(err, IsNil)
c.Assert(bundle.Rules, HasLen, 3)
c.Assert(bundle.Rules[0].ID, Equals, "0")
c.Assert(bundle.Rules[1].ID, Equals, "1")
c.Assert(bundle.Rules[2].ID, Equals, "follower")
c.Assert(bundle.Rules[2].Count, Equals, 9)
c.Assert(bundle.Rules[2].Constraints, DeepEquals, Constraints{
{
Op: NotIn,
Key: EngineLabelKey,
Values: []string{EngineLabelTiFlash},
},
})
}
err = bundle.Tidy()
chkfunc()
// tidy again
// it should be stable
err = bundle.Tidy()
chkfunc()
// tidy again
// it should be stable
bundle2 := bundle.Clone()
err = bundle2.Tidy()
c.Assert(err, IsNil)
c.Assert(bundle2, DeepEquals, bundle)
bundle.Rules[2].Constraints = append(bundle.Rules[2].Constraints, Constraint{
Op: In,
Key: EngineLabelKey,
Values: []string{EngineLabelTiFlash},
})
c.Log(bundle.Rules[2])
err = bundle.Tidy()
c.Assert(errors.Is(err, ErrConflictingConstraints), IsTrue)
}