diff --git a/executor/show.go b/executor/show.go index 5d7f721a4dd4b..d751ad4c92b3c 100644 --- a/executor/show.go +++ b/executor/show.go @@ -215,6 +215,8 @@ func (e *ShowExec) fetchAll(ctx context.Context) error { return e.fetchShowBRIE(ast.BRIEKindRestore) case ast.ShowPlacementLabels: return e.fetchShowPlacementLabels(ctx) + case ast.ShowPlacement: + return e.fetchShowPlacement(ctx) } return nil } diff --git a/executor/show_placement.go b/executor/show_placement.go index d074deca6e7e4..32da7f6d8827d 100644 --- a/executor/show_placement.go +++ b/executor/show_placement.go @@ -127,3 +127,24 @@ func (e *ShowExec) fetchShowPlacementLabels(ctx context.Context) error { return nil } + +func (e *ShowExec) fetchShowPlacement(_ context.Context) error { + err := e.fetchAllPlacementPolicies() + if err != nil { + return err + } + + return nil +} + +func (e *ShowExec) fetchAllPlacementPolicies() error { + policies := e.is.AllPlacementPolicies() + sort.Slice(policies, func(i, j int) bool { return policies[i].Name.O < policies[j].Name.O }) + for _, policy := range policies { + name := policy.Name + settings := policy.PlacementSettings + e.appendRow([]interface{}{"POLICY " + name.String(), settings.String(), "SCHEDULED"}) + } + + return nil +} diff --git a/executor/show_placement_labels_test.go b/executor/show_placement_labels_test.go new file mode 100644 index 0000000000000..3e3efcd865738 --- /dev/null +++ b/executor/show_placement_labels_test.go @@ -0,0 +1,85 @@ +// 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, +// 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 executor + +import ( + gjson "encoding/json" + + . "github.com/pingcap/check" + "github.com/pingcap/tidb/store/helper" + "github.com/pingcap/tidb/types/json" +) + +var _ = SerialSuites(&testShowPlacementLabelSuit{}) + +type testShowPlacementLabelSuit struct { +} + +func (s *testShowPlacementLabelSuit) TestShowPlacementLabelsBuilder(c *C) { + cases := []struct { + stores [][]*helper.StoreLabel + expects [][]interface{} + }{ + { + stores: nil, + expects: nil, + }, + { + stores: [][]*helper.StoreLabel{ + {{Key: "zone", Value: "z1"}, {Key: "rack", Value: "r3"}, {Key: "host", Value: "h1"}}, + {{Key: "zone", Value: "z1"}, {Key: "rack", Value: "r1"}, {Key: "host", Value: "h2"}}, + {{Key: "zone", Value: "z1"}, {Key: "rack", Value: "r2"}, {Key: "host", Value: "h2"}}, + {{Key: "zone", Value: "z2"}, {Key: "rack", Value: "r1"}, {Key: "host", Value: "h2"}}, + nil, + {{Key: "k1", Value: "v1"}}, + }, + expects: [][]interface{}{ + {"host", []string{"h1", "h2"}}, + {"k1", []string{"v1"}}, + {"rack", []string{"r1", "r2", "r3"}}, + {"zone", []string{"z1", "z2"}}, + }, + }, + } + + b := &showPlacementLabelsResultBuilder{} + toBinaryJSON := func(obj interface{}) (bj json.BinaryJSON) { + d, err := gjson.Marshal(obj) + c.Assert(err, IsNil) + err = bj.UnmarshalJSON(d) + c.Assert(err, IsNil) + return + } + + for _, ca := range cases { + for _, store := range ca.stores { + bj := toBinaryJSON(store) + err := b.AppendStoreLabels(bj) + c.Assert(err, IsNil) + } + + rows, err := b.BuildRows() + c.Assert(err, IsNil) + c.Assert(len(rows), Equals, len(ca.expects)) + for idx, expect := range ca.expects { + row := rows[idx] + bj := toBinaryJSON(expect[1]) + + c.Assert(row[0].(string), Equals, expect[0].(string)) + c.Assert(row[1].(json.BinaryJSON).TypeCode, Equals, bj.TypeCode) + c.Assert(row[1].(json.BinaryJSON).Value, BytesEquals, bj.Value) + } + } +} diff --git a/executor/show_placement_test.go b/executor/show_placement_test.go index 6b9303d59d37f..c51a1292ea396 100644 --- a/executor/show_placement_test.go +++ b/executor/show_placement_test.go @@ -12,74 +12,55 @@ // See the License for the specific language governing permissions and // limitations under the License. -package executor +package executor_test import ( - gjson "encoding/json" - . "github.com/pingcap/check" - "github.com/pingcap/tidb/store/helper" - "github.com/pingcap/tidb/types/json" + "github.com/pingcap/tidb/util/testkit" ) -var _ = SerialSuites(&testShowPlacementSuit{}) +func (s *testSuite5) TestShowPlacement(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("drop placement policy if exists p1") -type testShowPlacementSuit struct { -} + tk.MustExec("create placement policy pa1 " + + "PRIMARY_REGION=\"cn-east-1\" " + + "REGIONS=\"cn-east-1,cn-east-2\"" + + "SCHEDULE=\"EVEN\"") + defer tk.MustExec("drop placement policy pa1") + + tk.MustExec("create placement policy pa2 " + + "LEADER_CONSTRAINTS=\"[+region=us-east-1]\" " + + "FOLLOWER_CONSTRAINTS=\"[+region=us-east-2]\" " + + "FOLLOWERS=3") + defer tk.MustExec("drop placement policy pa2") -func (s *testPartitionSuite) TestShowPlacementLabelsBuilder(c *C) { - cases := []struct { - stores [][]*helper.StoreLabel - expects [][]interface{} - }{ - { - stores: nil, - expects: nil, - }, - { - stores: [][]*helper.StoreLabel{ - {{Key: "zone", Value: "z1"}, {Key: "rack", Value: "r3"}, {Key: "host", Value: "h1"}}, - {{Key: "zone", Value: "z1"}, {Key: "rack", Value: "r1"}, {Key: "host", Value: "h2"}}, - {{Key: "zone", Value: "z1"}, {Key: "rack", Value: "r2"}, {Key: "host", Value: "h2"}}, - {{Key: "zone", Value: "z2"}, {Key: "rack", Value: "r1"}, {Key: "host", Value: "h2"}}, - nil, - {{Key: "k1", Value: "v1"}}, - }, - expects: [][]interface{}{ - {"host", []string{"h1", "h2"}}, - {"k1", []string{"v1"}}, - {"rack", []string{"r1", "r2", "r3"}}, - {"zone", []string{"z1", "z2"}}, - }, - }, - } + tk.MustExec("create placement policy pb1 " + + "VOTER_CONSTRAINTS=\"[+region=bj]\" " + + "LEARNER_CONSTRAINTS=\"[+region=sh]\" " + + "CONSTRAINTS=\"[+disk=ssd]\"" + + "VOTERS=5 " + + "LEARNERS=3") + defer tk.MustExec("drop placement policy pb1") - b := &showPlacementLabelsResultBuilder{} - toBinaryJSON := func(obj interface{}) (bj json.BinaryJSON) { - d, err := gjson.Marshal(obj) - c.Assert(err, IsNil) - err = bj.UnmarshalJSON(d) - c.Assert(err, IsNil) - return - } + tk.MustQuery("show placement").Check(testkit.Rows( + "POLICY pa1 PRIMARY_REGION=\"cn-east-1\" REGIONS=\"cn-east-1,cn-east-2\" SCHEDULE=\"EVEN\" SCHEDULED", + "POLICY pa2 LEADER_CONSTRAINTS=\"[+region=us-east-1]\" FOLLOWERS=3 FOLLOWER_CONSTRAINTS=\"[+region=us-east-2]\" SCHEDULED", + "POLICY pb1 CONSTRAINTS=\"[+disk=ssd]\" VOTERS=5 VOTER_CONSTRAINTS=\"[+region=bj]\" LEARNERS=3 LEARNER_CONSTRAINTS=\"[+region=sh]\" SCHEDULED", + )) - for _, ca := range cases { - for _, store := range ca.stores { - bj := toBinaryJSON(store) - err := b.AppendStoreLabels(bj) - c.Assert(err, IsNil) - } + tk.MustQuery("show placement like 'POLICY%'").Check(testkit.Rows( + "POLICY pa1 PRIMARY_REGION=\"cn-east-1\" REGIONS=\"cn-east-1,cn-east-2\" SCHEDULE=\"EVEN\" SCHEDULED", + "POLICY pa2 LEADER_CONSTRAINTS=\"[+region=us-east-1]\" FOLLOWERS=3 FOLLOWER_CONSTRAINTS=\"[+region=us-east-2]\" SCHEDULED", + "POLICY pb1 CONSTRAINTS=\"[+disk=ssd]\" VOTERS=5 VOTER_CONSTRAINTS=\"[+region=bj]\" LEARNERS=3 LEARNER_CONSTRAINTS=\"[+region=sh]\" SCHEDULED", + )) - rows, err := b.BuildRows() - c.Assert(err, IsNil) - c.Assert(len(rows), Equals, len(ca.expects)) - for idx, expect := range ca.expects { - row := rows[idx] - bj := toBinaryJSON(expect[1]) + tk.MustQuery("show placement like 'POLICY pa%'").Check(testkit.Rows( + "POLICY pa1 PRIMARY_REGION=\"cn-east-1\" REGIONS=\"cn-east-1,cn-east-2\" SCHEDULE=\"EVEN\" SCHEDULED", + "POLICY pa2 LEADER_CONSTRAINTS=\"[+region=us-east-1]\" FOLLOWERS=3 FOLLOWER_CONSTRAINTS=\"[+region=us-east-2]\" SCHEDULED", + )) - c.Assert(row[0].(string), Equals, expect[0].(string)) - c.Assert(row[1].(json.BinaryJSON).TypeCode, Equals, bj.TypeCode) - c.Assert(row[1].(json.BinaryJSON).Value, BytesEquals, bj.Value) - } - } + tk.MustQuery("show placement where Target='POLICY pb1'").Check(testkit.Rows( + "POLICY pb1 CONSTRAINTS=\"[+disk=ssd]\" VOTERS=5 VOTER_CONSTRAINTS=\"[+region=bj]\" LEARNERS=3 LEARNER_CONSTRAINTS=\"[+region=sh]\" SCHEDULED", + )) } diff --git a/infoschema/builder.go b/infoschema/builder.go index 4277356c999e5..c2919c661e97a 100644 --- a/infoschema/builder.go +++ b/infoschema/builder.go @@ -537,7 +537,7 @@ func (b *Builder) copyPoliciesMap(oldIS *infoSchema) { is := b.is is.policyMutex.Lock() defer is.policyMutex.Unlock() - for _, v := range oldIS.PlacementPolicies() { + for _, v := range oldIS.AllPlacementPolicies() { is.policyMap[v.Name.L] = v } } diff --git a/infoschema/infoschema.go b/infoschema/infoschema.go index 070350c5c9394..bf7ef8dcce578 100644 --- a/infoschema/infoschema.go +++ b/infoschema/infoschema.go @@ -58,6 +58,8 @@ type InfoSchema interface { SetBundle(*placement.Bundle) // RuleBundles will return a copy of all rule bundles. RuleBundles() []*placement.Bundle + // AllPlacementPolicies returns all placement policies + AllPlacementPolicies() []*placementpolicy.PolicyInfo } type sortedTables []table.Table @@ -373,7 +375,8 @@ func (is *infoSchema) PolicyByName(name model.CIStr) (*placementpolicy.PolicyInf return t, r } -func (is *infoSchema) PlacementPolicies() []*placementpolicy.PolicyInfo { +// AllPlacementPolicies returns all placement policies +func (is *infoSchema) AllPlacementPolicies() []*placementpolicy.PolicyInfo { is.policyMutex.RLock() defer is.policyMutex.RUnlock() policies := make([]*placementpolicy.PolicyInfo, 0, len(is.policyMap)) diff --git a/meta/meta_test.go b/meta/meta_test.go index d5512a1145ced..13146351ccc6d 100644 --- a/meta/meta_test.go +++ b/meta/meta_test.go @@ -52,15 +52,17 @@ func TestPlacementPolicy(t *testing.T) { // test the meta storage of placemnt policy. policy := &placementpolicy.PolicyInfo{ - Name: model.NewCIStr("aa"), - PrimaryRegion: "my primary", - Regions: "my regions", - Learners: 1, - Followers: 2, - Voters: 3, - Schedule: "even", - Constraints: "+disk=ssd", - LearnerConstraints: "+zone=shanghai", + Name: model.NewCIStr("aa"), + PlacementSettings: placementpolicy.PlacementSettings{ + PrimaryRegion: "my primary", + Regions: "my regions", + Learners: 1, + Followers: 2, + Voters: 3, + Schedule: "even", + Constraints: "+disk=ssd", + LearnerConstraints: "+zone=shanghai", + }, } err = m.CreatePolicy(policy) require.NoError(t, err) diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index e66920a7f5ecf..fe9f9046f249b 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -4130,6 +4130,9 @@ func buildShowSchema(s *ast.ShowStmt, isView bool, isSequence bool) (schema *exp case ast.ShowPlacementLabels: names = []string{"Key", "Values"} ftypes = []byte{mysql.TypeVarchar, mysql.TypeJSON} + case ast.ShowPlacement: + names = []string{"Target", "Placement", "Scheduling_state"} + ftypes = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar} } schema = expression.NewSchema(make([]*expression.Column, 0, len(names))...) diff --git a/util/placementpolicy/policy.go b/util/placementpolicy/policy.go index c08b03ecd493f..6e485a1852ba4 100644 --- a/util/placementpolicy/policy.go +++ b/util/placementpolicy/policy.go @@ -15,23 +15,87 @@ package placementpolicy import ( + "fmt" + "strings" + "github.com/pingcap/parser/model" ) +// PlacementSettings is the settings of the placement +type PlacementSettings struct { + PrimaryRegion string `json:"primary_region"` + Regions string `json:"regions"` + Learners uint64 `json:"learners"` + Followers uint64 `json:"followers"` + Voters uint64 `json:"voters"` + Schedule string `json:"schedule"` + Constraints string `json:"constraints"` + LeaderConstraints string `json:"leader_constraints"` + LearnerConstraints string `json:"learner_constraints"` + FollowerConstraints string `json:"follower_constraints"` + VoterConstraints string `json:"voter_constraints"` +} + +func writeSettingItemToBuilder(sb *strings.Builder, item string) { + if sb.Len() != 0 { + sb.WriteString(" ") + } + sb.WriteString(item) +} + +func (p *PlacementSettings) String() string { + sb := new(strings.Builder) + if len(p.PrimaryRegion) > 0 { + writeSettingItemToBuilder(sb, fmt.Sprintf("PRIMARY_REGION=\"%s\"", p.PrimaryRegion)) + } + + if len(p.Regions) > 0 { + writeSettingItemToBuilder(sb, fmt.Sprintf("REGIONS=\"%s\"", p.Regions)) + } + + if len(p.Schedule) > 0 { + writeSettingItemToBuilder(sb, fmt.Sprintf("SCHEDULE=\"%s\"", p.Schedule)) + } + + if len(p.Constraints) > 0 { + writeSettingItemToBuilder(sb, fmt.Sprintf("CONSTRAINTS=\"%s\"", p.Constraints)) + } + + if len(p.LeaderConstraints) > 0 { + writeSettingItemToBuilder(sb, fmt.Sprintf("LEADER_CONSTRAINTS=\"%s\"", p.LeaderConstraints)) + } + + if p.Voters > 0 { + writeSettingItemToBuilder(sb, fmt.Sprintf("VOTERS=%d", p.Voters)) + } + + if len(p.VoterConstraints) > 0 { + writeSettingItemToBuilder(sb, fmt.Sprintf("VOTER_CONSTRAINTS=\"%s\"", p.VoterConstraints)) + } + + if p.Followers > 0 { + writeSettingItemToBuilder(sb, fmt.Sprintf("FOLLOWERS=%d", p.Followers)) + } + + if len(p.FollowerConstraints) > 0 { + writeSettingItemToBuilder(sb, fmt.Sprintf("FOLLOWER_CONSTRAINTS=\"%s\"", p.FollowerConstraints)) + } + + if p.Learners > 0 { + writeSettingItemToBuilder(sb, fmt.Sprintf("LEARNERS=%d", p.Learners)) + } + + if len(p.LearnerConstraints) > 0 { + writeSettingItemToBuilder(sb, fmt.Sprintf("LEARNER_CONSTRAINTS=\"%s\"", p.LearnerConstraints)) + } + + return sb.String() +} + // PolicyInfo is the struct to store the placement policy. type PolicyInfo struct { - ID int64 `json:"id"` - Name model.CIStr `json:"name"` - PrimaryRegion string `json:"primary_region"` - Regions string `json:"regions"` - Learners uint64 `json:"learners"` - Followers uint64 `json:"followers"` - Voters uint64 `json:"voters"` - Schedule string `json:"schedule"` - Constraints string `json:"constraints"` - LeaderConstraints string `json:"leader_constraints"` - LearnerConstraints string `json:"learner_constraints"` - FollowerConstraints string `json:"follower_constraints"` - VoterConstraints string `json:"voter_constraints"` - State model.SchemaState `json:"state"` + PlacementSettings + ID int64 `json:"id"` + Name model.CIStr `json:"name"` + State model.SchemaState `json:"state"` } diff --git a/util/placementpolicy/policy_test.go b/util/placementpolicy/policy_test.go new file mode 100644 index 0000000000000..aa6e305c56486 --- /dev/null +++ b/util/placementpolicy/policy_test.go @@ -0,0 +1,62 @@ +// 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, +// 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 placementpolicy + +import ( + "testing" + + "github.com/pingcap/tidb/util/testbridge" + "github.com/stretchr/testify/assert" + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + testbridge.WorkaroundGoCheckFlags() + goleak.VerifyTestMain(m) +} + +func TestPlacementSettingsString(t *testing.T) { + assert := assert.New(t) + + settings := &PlacementSettings{ + PrimaryRegion: "us-east-1", + Regions: "us-east-1,us-east-2", + Schedule: "EVEN", + } + assert.Equal("PRIMARY_REGION=\"us-east-1\" REGIONS=\"us-east-1,us-east-2\" SCHEDULE=\"EVEN\"", settings.String()) + + settings = &PlacementSettings{ + LeaderConstraints: "[+region=bj]", + } + assert.Equal("LEADER_CONSTRAINTS=\"[+region=bj]\"", settings.String()) + + settings = &PlacementSettings{ + Voters: 1, + VoterConstraints: "[+region=us-east-1]", + Followers: 2, + FollowerConstraints: "[+disk=ssd]", + Learners: 3, + LearnerConstraints: "[+region=us-east-2]", + } + assert.Equal("VOTERS=1 VOTER_CONSTRAINTS=\"[+region=us-east-1]\" FOLLOWERS=2 FOLLOWER_CONSTRAINTS=\"[+disk=ssd]\" LEARNERS=3 LEARNER_CONSTRAINTS=\"[+region=us-east-2]\"", settings.String()) + + settings = &PlacementSettings{ + Voters: 3, + Followers: 2, + Learners: 1, + Constraints: "{+us-east-1:1,+us-east-2:1}", + } + assert.Equal("CONSTRAINTS=\"{+us-east-1:1,+us-east-2:1}\" VOTERS=3 FOLLOWERS=2 LEARNERS=1", settings.String()) +}