diff --git a/agent/config/builder.go b/agent/config/builder.go index 665688b8c864..1b79bb0f98e9 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -1290,6 +1290,10 @@ func (b *builder) validate(rt RuntimeConfig) error { "1 and 63 bytes.", rt.NodeName) } + if err := rt.StructLocality().Validate(); err != nil { + return fmt.Errorf("locality is invalid: %s", err) + } + if ipaddr.IsAny(rt.AdvertiseAddrLAN.IP) { return fmt.Errorf("Advertise address cannot be 0.0.0.0, :: or [::]") } diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index c1cd85ac502f..cbc1e904d254 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -1036,6 +1036,13 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { }, }, }) + run(t, testCase{ + desc: "locality invalid", + args: []string{`-data-dir=` + dataDir}, + json: []string{`{"locality": {"zone": "us-west-1a"}}`}, + hcl: []string{`locality { zone = "us-west-1a" }`}, + expectedErr: "locality is invalid: zone cannot be set without region", + }) run(t, testCase{ desc: "client addr and ports == 0", args: []string{`-data-dir=` + dataDir}, diff --git a/agent/structs/structs.go b/agent/structs/structs.go index b356a31aeaf9..1499c35eb80d 100644 --- a/agent/structs/structs.go +++ b/agent/structs/structs.go @@ -1480,6 +1480,10 @@ func (s *NodeService) IsGateway() bool { func (s *NodeService) Validate() error { var result error + if err := s.Locality.Validate(); err != nil { + result = multierror.Append(result, err) + } + if s.Kind == ServiceKindConnectProxy { if s.Port == 0 && s.SocketPath == "" { result = multierror.Append(result, fmt.Errorf("Port or SocketPath must be set for a %s", s.Kind)) @@ -3111,3 +3115,15 @@ func (l *Locality) GetRegion() string { } return l.Region } + +func (l *Locality) Validate() error { + if l == nil { + return nil + } + + if l.Region == "" && l.Zone != "" { + return fmt.Errorf("zone cannot be set without region") + } + + return nil +} diff --git a/agent/structs/structs_test.go b/agent/structs/structs_test.go index 6d887da9ac77..668f5fb08fae 100644 --- a/agent/structs/structs_test.go +++ b/agent/structs/structs_test.go @@ -592,6 +592,43 @@ func TestStructs_ServiceNode_Conversions(t *testing.T) { } } +func TestStructs_Locality_Validate(t *testing.T) { + type testCase struct { + locality *Locality + err string + } + cases := map[string]testCase{ + "nil": { + nil, + "", + }, + "region only": { + &Locality{Region: "us-west-1"}, + "", + }, + "region and zone": { + &Locality{Region: "us-west-1", Zone: "us-west-1a"}, + "", + }, + "zone only": { + &Locality{Zone: "us-west-1a"}, + "zone cannot be set without region", + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + err := tc.locality.Validate() + if tc.err == "" { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.err) + } + }) + } +} + func TestStructs_NodeService_ValidateMeshGateway(t *testing.T) { type testCase struct { Modify func(*NodeService) @@ -1152,6 +1189,13 @@ func TestStructs_NodeService_ValidateConnectProxy(t *testing.T) { }, "", }, + { + "connect-proxy: invalid locality", + func(x *NodeService) { + x.Locality = &Locality{Zone: "bad"} + }, + "zone cannot be set without region", + }, } for _, tc := range cases {