-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
region_config.go
145 lines (128 loc) · 4.48 KB
/
region_config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright 2021 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package multiregion
import (
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
"github.com/cockroachdb/errors"
)
// minNumRegionsForSurviveRegionalGoal is the minimum number of regions that a
// a database must have to survive a REGION failure.
const minNumRegionsForSurviveRegionGoal = 3
// RegionConfig represents the user configured state of a multi-region database.
type RegionConfig struct {
survivalGoal descpb.SurvivalGoal
regions descpb.RegionNames
primaryRegion descpb.RegionName
}
// SurvivalGoal returns the survival goal configured on the RegionConfig.
func (r *RegionConfig) SurvivalGoal() descpb.SurvivalGoal {
return r.survivalGoal
}
// PrimaryRegion returns the primary region configured on the RegionConfig.
func (r *RegionConfig) PrimaryRegion() descpb.RegionName {
return r.primaryRegion
}
// Regions returns the list of regions added to the RegionConfig.
func (r *RegionConfig) Regions() descpb.RegionNames {
return r.regions
}
// IsValidRegionNameString implements the tree.DatabaseRegionConfig interface.
func (r *RegionConfig) IsValidRegionNameString(regionStr string) bool {
if r == nil {
return false
}
for _, region := range r.Regions() {
if string(region) == regionStr {
return true
}
}
return false
}
// PrimaryRegionString implements the tree.DatabaseRegionConfig interface.
func (r *RegionConfig) PrimaryRegionString() string {
if r == nil {
return ""
}
return string(r.PrimaryRegion())
}
// NewRegionConfig constructs a RegionConfig.
func NewRegionConfig(
regions descpb.RegionNames, primaryRegion descpb.RegionName, survivalGoal descpb.SurvivalGoal,
) *RegionConfig {
return &RegionConfig{
regions: regions,
primaryRegion: primaryRegion,
survivalGoal: survivalGoal,
}
}
// EnsureSurvivalGoalIsSatisfiable returns an error if the survivability goal is
// unsatisfiable.
func (r *RegionConfig) EnsureSurvivalGoalIsSatisfiable() error {
// If we're changing to survive a region failure, validate that we have enough
// regions in the database.
if r.SurvivalGoal() == descpb.SurvivalGoal_REGION_FAILURE {
if len(r.Regions()) < minNumRegionsForSurviveRegionGoal {
return errors.WithHintf(
pgerror.Newf(pgcode.InvalidName,
"at least %d regions are required for surviving a region failure",
minNumRegionsForSurviveRegionGoal,
),
"you must add additional regions to the database using "+
"ALTER DATABASE <db_name> ADD REGION <region_name>",
)
}
}
return nil
}
// InitializationRegionConfig is a wrapper around RegionConfig containing
// additional fields which are only required during initialization.
type InitializationRegionConfig struct {
RegionConfig
regionEnumID descpb.ID
}
// NewInitializationRegionConfig constructs a region config capable of
// initializing a multi-region database.
func NewInitializationRegionConfig(
regions descpb.RegionNames,
primaryRegion descpb.RegionName,
survivalGoal descpb.SurvivalGoal,
regionEnumID descpb.ID,
) *InitializationRegionConfig {
regionConfig := NewRegionConfig(regions, primaryRegion, survivalGoal)
return &InitializationRegionConfig{
RegionConfig: *regionConfig,
regionEnumID: regionEnumID,
}
}
// RegionEnumID returns the multi-region enum ID.
func (i *InitializationRegionConfig) RegionEnumID() descpb.ID {
return i.regionEnumID
}
// ValidateInitializationRegionConfig validates that the given
// InitializationRegionConfig is valid.
func ValidateInitializationRegionConfig(config *InitializationRegionConfig) error {
if config.regionEnumID == descpb.InvalidID {
return errors.AssertionFailedf("expected a valid multi-region enum ID to be initialized")
}
if len(config.regions) == 0 {
return errors.AssertionFailedf("expected > 0 number of regions in the region config")
}
if config.survivalGoal == descpb.SurvivalGoal_REGION_FAILURE &&
len(config.regions) < minNumRegionsForSurviveRegionGoal {
return pgerror.Newf(
pgcode.InvalidParameterValue,
"at least %d regions are required for surviving a region failure",
minNumRegionsForSurviveRegionGoal,
)
}
return nil
}