Skip to content

Commit 1270692

Browse files
refactor: refactor the domain / cli commands for active-active's cluster-attr (#7299)
<!-- Describe what has changed in this PR --> **What changed?** This is changing the cli to update the cluster-attributes for active/active clusters. This allows domains to update the domain by passing in the format `<cluster-attr>.<scope>:<name>` ie: `region.manilla:cluster0,region.newyork:cluster1'` <!-- Tell your future self why have you made these changes --> **Why?** <!-- How have you verified this change? Tested locally? Added a unit test? Checked in staging env? --> **How did you test it?** <!-- Assuming the worst case, what can be broken when deploying this change to production? --> **Potential risks** <!-- Is it notable for release? e.g. schema updates, configuration or data migration required? If so, please mention it, and also update CHANGELOG.md --> **Release notes** <!-- Is there any documentation updates should be made for config, https://cadenceworkflow.io/docs/operation-guide/setup/ ? If so, please open an PR in https://github.com/cadence-workflow/cadence-docs --> **Documentation Changes** --------- Signed-off-by: David Porter <[email protected]>
1 parent 2ec91f2 commit 1270692

File tree

6 files changed

+245
-105
lines changed

6 files changed

+245
-105
lines changed

common/types/shared.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1710,6 +1710,13 @@ type DescribeDomainResponse struct {
17101710
FailoverInfo *FailoverInfo `json:"failoverInfo,omitempty"`
17111711
}
17121712

1713+
func (v *DomainReplicationConfiguration) GetActiveClusters() (o *ActiveClusters) {
1714+
if v != nil && v.ActiveClusters != nil {
1715+
return v.ActiveClusters
1716+
}
1717+
return
1718+
}
1719+
17131720
// GetDomainInfo is an internal getter (TBD...)
17141721
func (v *DescribeDomainResponse) GetDomainInfo() (o *DomainInfo) {
17151722
if v != nil && v.DomainInfo != nil {
@@ -2253,6 +2260,20 @@ type DomainReplicationConfiguration struct {
22532260
ActiveClusters *ActiveClusters `json:"activeClusters,omitempty"`
22542261
}
22552262

2263+
func (v *DomainReplicationConfiguration) IsActiveActiveDomain() bool {
2264+
if v == nil || v.ActiveClusters == nil {
2265+
return false
2266+
}
2267+
if v.ActiveClusters.AttributeScopes != nil && len(v.ActiveClusters.AttributeScopes) > 0 {
2268+
return true
2269+
}
2270+
// todo (david.porter) remove this once we have fully migrated to AttributeScopes
2271+
if v.ActiveClusters.ActiveClustersByRegion != nil && len(v.ActiveClusters.ActiveClustersByRegion) > 0 {
2272+
return true
2273+
}
2274+
return false
2275+
}
2276+
22562277
// GetActiveClusterName is an internal getter (TBD...)
22572278
func (v *DomainReplicationConfiguration) GetActiveClusterName() (o string) {
22582279
if v != nil {
@@ -2269,13 +2290,6 @@ func (v *DomainReplicationConfiguration) GetClusters() (o []*ClusterReplicationC
22692290
return
22702291
}
22712292

2272-
func (v *DomainReplicationConfiguration) GetActiveClusters() (o *ActiveClusters) {
2273-
if v != nil && v.ActiveClusters != nil {
2274-
return v.ActiveClusters
2275-
}
2276-
return
2277-
}
2278-
22792293
// ByteSize returns the approximate memory used in bytes
22802294
func (v *DomainReplicationConfiguration) ByteSize() uint64 {
22812295
if v == nil {

common/types/shared_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,77 @@ func TestActiveClustersConfigDeepCopy(t *testing.T) {
120120
}
121121
}
122122

123+
func TestIsActiveActiveDomain(t *testing.T) {
124+
tests := []struct {
125+
name string
126+
activeClusters *DomainReplicationConfiguration
127+
want bool
128+
}{
129+
{
130+
name: "empty DomainReplicationConfiguration should return false",
131+
activeClusters: &DomainReplicationConfiguration{},
132+
want: false,
133+
},
134+
{
135+
name: "nil receiver should return false",
136+
activeClusters: nil,
137+
want: false,
138+
},
139+
{
140+
name: "empty ActiveClusters should return false",
141+
activeClusters: &DomainReplicationConfiguration{ActiveClusters: &ActiveClusters{}},
142+
want: false,
143+
},
144+
{
145+
name: "ActiveClusters with only old format populated should return true",
146+
activeClusters: &DomainReplicationConfiguration{
147+
ActiveClusters: &ActiveClusters{
148+
ActiveClustersByRegion: map[string]ActiveClusterInfo{
149+
"us-east-1": {ActiveClusterName: "cluster1"},
150+
},
151+
},
152+
},
153+
want: true,
154+
},
155+
{
156+
name: "ActiveClusters with only new format populated should return true",
157+
activeClusters: &DomainReplicationConfiguration{
158+
ActiveClusters: &ActiveClusters{
159+
AttributeScopes: map[string]ClusterAttributeScope{
160+
"region": {ClusterAttributes: map[string]ActiveClusterInfo{
161+
"us-east-1": {ActiveClusterName: "cluster1"},
162+
}},
163+
},
164+
},
165+
},
166+
want: true,
167+
},
168+
{
169+
name: "ActiveClusters with both formats populated should return true",
170+
activeClusters: &DomainReplicationConfiguration{
171+
ActiveClusters: &ActiveClusters{
172+
ActiveClustersByRegion: map[string]ActiveClusterInfo{
173+
"us-east-1": {ActiveClusterName: "cluster1"},
174+
},
175+
AttributeScopes: map[string]ClusterAttributeScope{
176+
"region": {ClusterAttributes: map[string]ActiveClusterInfo{
177+
"us-east-1": {ActiveClusterName: "cluster1"},
178+
}},
179+
},
180+
},
181+
},
182+
want: true,
183+
},
184+
}
185+
186+
for _, tt := range tests {
187+
t.Run(tt.name, func(t *testing.T) {
188+
got := tt.activeClusters.IsActiveActiveDomain()
189+
assert.Equal(t, tt.want, got)
190+
})
191+
}
192+
}
193+
123194
// identicalByteArray returns true if a and b are the same slice, false otherwise.
124195
func identicalByteArray(a, b []byte) bool {
125196
return len(a) == len(b) && unsafe.SliceData(a) == unsafe.SliceData(b)

tools/cli/domain_commands.go

Lines changed: 67 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"errors"
2828
"fmt"
2929
"os"
30+
"regexp"
3031
"strconv"
3132
"strings"
3233
"time"
@@ -112,16 +113,6 @@ func (d *domainCLIImpl) RegisterDomain(c *cli.Context) error {
112113
return commoncli.Problem(fmt.Sprintf("Option %s format is invalid.", FlagIsGlobalDomain), err)
113114
}
114115
}
115-
isActiveActiveDomain := false
116-
if c.IsSet(FlagIsActiveActiveDomain) {
117-
isActiveActiveDomain, err = strconv.ParseBool(c.String(FlagIsActiveActiveDomain))
118-
if err != nil {
119-
return commoncli.Problem(fmt.Sprintf("Option %s format is invalid.", FlagIsActiveActiveDomain), err)
120-
}
121-
122-
// Also set isGlobalDomain to true if it is active-active domain
123-
isGlobalDomain = isActiveActiveDomain
124-
}
125116

126117
var domainData *flag.StringMap
127118
if c.IsSet(FlagDomainData) {
@@ -136,17 +127,9 @@ func (d *domainCLIImpl) RegisterDomain(c *cli.Context) error {
136127

137128
activeClusterName := ""
138129
if c.IsSet(FlagActiveClusterName) {
139-
if isActiveActiveDomain {
140-
return commoncli.Problem("Option --active_cluster is not supported for active-active domain. Use --active_clusters_by_region instead.", nil)
141-
}
142130
activeClusterName = c.String(FlagActiveClusterName)
143131
}
144132

145-
activeClustersByRegion, err := parseActiveClustersByRegion(c, isActiveActiveDomain)
146-
if err != nil {
147-
return err
148-
}
149-
150133
var clusters []*types.ClusterReplicationConfiguration
151134
if c.IsSet(FlagClusters) {
152135
for _, clusterStr := range c.StringSlice(FlagClusters) {
@@ -165,6 +148,15 @@ func (d *domainCLIImpl) RegisterDomain(c *cli.Context) error {
165148
return fmt.Errorf("failed to parse %s flag: %w", FlagVisibilityArchivalStatus, err)
166149
}
167150

151+
var activeClusters *types.ActiveClusters
152+
if c.IsSet(FlagActiveClusters) {
153+
ac, err := parseActiveClustersByClusterAttribute(c.String(FlagActiveClusters))
154+
if err != nil {
155+
return err
156+
}
157+
activeClusters = &ac
158+
}
159+
168160
request := &types.RegisterDomainRequest{
169161
Name: domainName,
170162
Description: description,
@@ -173,7 +165,7 @@ func (d *domainCLIImpl) RegisterDomain(c *cli.Context) error {
173165
WorkflowExecutionRetentionPeriodInDays: int32(retentionDays),
174166
Clusters: clusters,
175167
ActiveClusterName: activeClusterName,
176-
ActiveClustersByRegion: activeClustersByRegion,
168+
ActiveClusters: activeClusters,
177169
SecurityToken: securityToken,
178170
HistoryArchivalStatus: has,
179171
HistoryArchivalURI: c.String(FlagHistoryArchivalURI),
@@ -225,23 +217,16 @@ func (d *domainCLIImpl) UpdateDomain(c *cli.Context) error {
225217
ActiveClusterName: common.StringPtr(activeCluster),
226218
FailoverTimeoutInSeconds: failoverTimeout,
227219
}
228-
} else if c.IsSet(FlagActiveClustersByRegion) { // active-active domain failover
229-
activeClustersByRegion, err := parseActiveClustersByRegion(c, true)
220+
} else if c.IsSet(FlagActiveClusters) { // active-active domain failover
221+
activeClusters, err := parseActiveClustersByClusterAttribute(c.String(FlagActiveClusters))
230222
if err != nil {
231223
return err
232224
}
233225

234-
acbr := make(map[string]types.ActiveClusterInfo)
235-
for region, cluster := range activeClustersByRegion {
236-
acbr[region] = types.ActiveClusterInfo{
237-
ActiveClusterName: cluster,
238-
}
239-
}
240-
241226
updateRequest = &types.UpdateDomainRequest{
242227
Name: domainName,
243228
ActiveClusters: &types.ActiveClusters{
244-
ActiveClustersByRegion: acbr,
229+
AttributeScopes: activeClusters.AttributeScopes,
245230
},
246231
}
247232
} else {
@@ -669,26 +654,27 @@ type ActiveClusterInfoRow struct {
669654
}
670655

671656
type DomainRow struct {
672-
Name string `header:"Name"`
673-
UUID string `header:"UUID"`
674-
Description string
675-
OwnerEmail string
676-
DomainData map[string]string `header:"Domain Data"`
677-
Status types.DomainStatus `header:"Status"`
678-
IsGlobal bool `header:"Is Global Domain"`
679-
ActiveCluster string `header:"Active Cluster"`
680-
Clusters []string `header:"Clusters"`
681-
RetentionDays int32 `header:"Retention Days"`
682-
EmitMetrics bool
683-
HistoryArchivalStatus types.ArchivalStatus `header:"History Archival Status"`
684-
HistoryArchivalURI string `header:"History Archival URI"`
685-
VisibilityArchivalStatus types.ArchivalStatus `header:"Visibility Archival Status"`
686-
VisibilityArchivalURI string `header:"Visibility Archival URI"`
687-
BadBinaries []BadBinaryRow
688-
FailoverInfo *FailoverInfoRow
689-
LongRunningWorkFlowNum *int
690-
IsActiveActiveDomain bool
691-
ActiveClustersByRegion []ActiveClusterInfoRow
657+
Name string `header:"Name"`
658+
UUID string `header:"UUID"`
659+
Description string
660+
OwnerEmail string
661+
DomainData map[string]string `header:"Domain Data"`
662+
Status types.DomainStatus `header:"Status"`
663+
IsGlobal bool `header:"Is Global Domain"`
664+
ActiveCluster string `header:"Active Cluster"`
665+
Clusters []string `header:"Clusters"`
666+
RetentionDays int32 `header:"Retention Days"`
667+
EmitMetrics bool
668+
HistoryArchivalStatus types.ArchivalStatus `header:"History Archival Status"`
669+
HistoryArchivalURI string `header:"History Archival URI"`
670+
VisibilityArchivalStatus types.ArchivalStatus `header:"Visibility Archival Status"`
671+
VisibilityArchivalURI string `header:"Visibility Archival URI"`
672+
BadBinaries []BadBinaryRow
673+
FailoverInfo *FailoverInfoRow
674+
LongRunningWorkFlowNum *int
675+
IsActiveActiveDomain bool
676+
ActiveClustersByRegion []ActiveClusterInfoRow // todo (david.porter) remove this as it's not in use
677+
ActiveClustersByClusterAttribute []ActiveClusterInfoRow
692678
}
693679

694680
type DomainMigrationRow struct {
@@ -732,7 +718,7 @@ func newDomainRow(domain *types.DescribeDomainResponse) DomainRow {
732718
VisibilityArchivalURI: domain.Configuration.GetVisibilityArchivalURI(),
733719
BadBinaries: newBadBinaryRows(domain.Configuration.BadBinaries),
734720
FailoverInfo: newFailoverInfoRow(domain.FailoverInfo),
735-
IsActiveActiveDomain: domain.ReplicationConfiguration.GetActiveClusters() != nil,
721+
IsActiveActiveDomain: domain.ReplicationConfiguration.IsActiveActiveDomain(),
736722
ActiveClustersByRegion: newActiveClustersByRegion(domain.ReplicationConfiguration.GetActiveClusters()),
737723
}
738724
}
@@ -972,27 +958,42 @@ func clustersToStrings(clusters []*types.ClusterReplicationConfiguration) []stri
972958
return res
973959
}
974960

975-
func parseActiveClustersByRegion(c *cli.Context, isActiveActiveDomain bool) (map[string]string, error) {
976-
var activeClustersByRegion map[string]string
977-
if c.IsSet(FlagActiveClustersByRegion) {
978-
if !isActiveActiveDomain {
979-
return nil, commoncli.Problem("Option --active_clusters_by_region is only supported for active-active domain. Use --active_cluster instead.", nil)
961+
func parseActiveClustersByClusterAttribute(clusters string) (types.ActiveClusters, error) {
962+
split := regexp.MustCompile(`(?P<attribute>[a-zA-Z0-9_]+).(?P<scope>[a-zA-Z0-9_]+):(?P<name>[a-zA-Z0-9_]+)`)
963+
matches := split.FindAllStringSubmatch(clusters, -1)
964+
if len(matches) == 0 {
965+
return types.ActiveClusters{}, fmt.Errorf("option %s format is invalid. Expected format is 'region.dca:dev2_dca,region.phx:dev2_phx'", FlagActiveClusters)
966+
}
967+
968+
out := types.ActiveClusters{
969+
AttributeScopes: map[string]types.ClusterAttributeScope{},
970+
}
971+
972+
for _, match := range matches {
973+
if len(match) != 4 {
974+
return types.ActiveClusters{}, fmt.Errorf("option %s format is invalid. Expected format is 'region.dca:dev2_dca,region.phx:dev2_phx'", FlagActiveClusters)
980975
}
976+
attribute := match[1]
977+
scope := match[2]
978+
name := match[3]
981979

982-
activeClustersByRegion = make(map[string]string)
983-
for _, regionCluster := range c.StringSlice(FlagActiveClustersByRegion) {
984-
splitted := strings.Split(regionCluster, ":")
985-
if len(splitted) != 2 {
986-
return nil, commoncli.Problem(fmt.Sprintf("Option --%s format is invalid. Expected format is 'region1:cluster1,region2:cluster2'", FlagActiveClustersByRegion), nil)
980+
existing, ok := out.AttributeScopes[attribute]
981+
if !ok {
982+
out.AttributeScopes[attribute] = types.ClusterAttributeScope{
983+
ClusterAttributes: map[string]types.ActiveClusterInfo{
984+
scope: {ActiveClusterName: name},
985+
},
987986
}
988-
region, cluster := strings.TrimSpace(splitted[0]), strings.TrimSpace(splitted[1])
989-
activeClustersByRegion[region] = cluster
987+
} else {
988+
if _, ok := existing.ClusterAttributes[scope]; ok {
989+
return types.ActiveClusters{}, fmt.Errorf("option active_clusters format is invalid. the key %q was duplicated. This can only map to a single active cluster", scope)
990+
}
991+
existing.ClusterAttributes[scope] = types.ActiveClusterInfo{ActiveClusterName: name}
992+
out.AttributeScopes[attribute] = existing
990993
}
991-
}
992994

993-
if isActiveActiveDomain && len(activeClustersByRegion) == 0 {
994-
return nil, commoncli.Problem("Option --active_clusters_by_region is required for active-active domain.", nil)
995+
out.AttributeScopes[attribute].ClusterAttributes[scope] = types.ActiveClusterInfo{ActiveClusterName: name}
995996
}
996997

997-
return activeClustersByRegion, nil
998+
return out, nil
998999
}

0 commit comments

Comments
 (0)