diff --git a/.changelog/12722.txt b/.changelog/12722.txt new file mode 100644 index 000000000..6c42e7984 --- /dev/null +++ b/.changelog/12722.txt @@ -0,0 +1,3 @@ +```release-note:feature +checks: add UDP health checks.. +``` diff --git a/agent/agent.go b/agent/agent.go index eed01ca5d..2056c8a5e 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -248,6 +248,9 @@ type Agent struct { // checkTCPs maps the check ID to an associated TCP check checkTCPs map[structs.CheckID]*checks.CheckTCP + // checkUDPs maps the check ID to an associated UDP check + checkUDPs map[structs.CheckID]*checks.CheckUDP + // checkGRPCs maps the check ID to an associated GRPC check checkGRPCs map[structs.CheckID]*checks.CheckGRPC @@ -401,6 +404,7 @@ func New(bd BaseDeps) (*Agent, error) { checkHTTPs: make(map[structs.CheckID]*checks.CheckHTTP), checkH2PINGs: make(map[structs.CheckID]*checks.CheckH2PING), checkTCPs: make(map[structs.CheckID]*checks.CheckTCP), + checkUDPs: make(map[structs.CheckID]*checks.CheckUDP), checkGRPCs: make(map[structs.CheckID]*checks.CheckGRPC), checkDockers: make(map[structs.CheckID]*checks.CheckDocker), checkAliases: make(map[structs.CheckID]*checks.CheckAlias), @@ -1497,6 +1501,9 @@ func (a *Agent) ShutdownAgent() error { for _, chk := range a.checkTCPs { chk.Stop() } + for _, chk := range a.checkUDPs { + chk.Stop() + } for _, chk := range a.checkGRPCs { chk.Stop() } @@ -2796,6 +2803,31 @@ func (a *Agent) addCheck(check *structs.HealthCheck, chkType *structs.CheckType, tcp.Start() a.checkTCPs[cid] = tcp + case chkType.IsUDP(): + if existing, ok := a.checkUDPs[cid]; ok { + existing.Stop() + delete(a.checkUDPs, cid) + } + if chkType.Interval < checks.MinInterval { + a.logger.Warn("check has interval below minimum", + "check", cid.String(), + "minimum_interval", checks.MinInterval, + ) + chkType.Interval = checks.MinInterval + } + + udp := &checks.CheckUDP{ + CheckID: cid, + ServiceID: sid, + UDP: chkType.UDP, + Interval: chkType.Interval, + Timeout: chkType.Timeout, + Logger: a.logger, + StatusHandler: statusHandler, + } + udp.Start() + a.checkUDPs[cid] = udp + case chkType.IsGRPC(): if existing, ok := a.checkGRPCs[cid]; ok { existing.Stop() @@ -3095,6 +3127,10 @@ func (a *Agent) cancelCheckMonitors(checkID structs.CheckID) { check.Stop() delete(a.checkTCPs, checkID) } + if check, ok := a.checkUDPs[checkID]; ok { + check.Stop() + delete(a.checkUDPs, checkID) + } if check, ok := a.checkGRPCs[checkID]; ok { check.Stop() delete(a.checkGRPCs, checkID) diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go index cfb7d0aa9..b8b96f3f7 100644 --- a/agent/agent_endpoint_test.go +++ b/agent/agent_endpoint_test.go @@ -2514,6 +2514,48 @@ func TestAgent_RegisterCheck(t *testing.T) { } } +func TestAgent_RegisterCheck_UDP(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + args := &structs.CheckDefinition{ + UDP: "1.1.1.1", + Name: "test", + Interval: 10 * time.Second, + } + req, _ := http.NewRequest("PUT", "/v1/agent/check/register?token=abc123", jsonReader(args)) + resp := httptest.NewRecorder() + a.srv.h.ServeHTTP(resp, req) + require.Equal(t, http.StatusOK, resp.Code) + + // Ensure we have a check mapping + checkID := structs.NewCheckID("test", nil) + if existing := a.State.Check(checkID); existing == nil { + t.Fatalf("missing test check") + } + + if _, ok := a.checkUDPs[checkID]; !ok { + t.Fatalf("missing test check udp") + } + + // Ensure the token was configured + if token := a.State.CheckToken(checkID); token == "" { + t.Fatalf("missing token") + } + + // By default, checks start in critical state. + state := a.State.Check(checkID) + if state.Status != api.HealthCritical { + t.Fatalf("bad: %v", state) + } +} + // This verifies all the forms of the new args-style check that we need to // support as a result of https://github.com/hashicorp/consul/issues/3587. func TestAgent_RegisterCheck_Scripts(t *testing.T) { @@ -3276,6 +3318,10 @@ func testAgent_RegisterService(t *testing.T, extraHCL string) { { TTL: 30 * time.Second, }, + { + UDP: "1.1.1.1", + Interval: 5 * time.Second, + }, }, Weights: &structs.Weights{ Passing: 100, @@ -3307,12 +3353,12 @@ func testAgent_RegisterService(t *testing.T, extraHCL string) { // Ensure we have a check mapping checks := a.State.Checks(structs.WildcardEnterpriseMetaInDefaultPartition()) - if len(checks) != 3 { + if len(checks) != 4 { t.Fatalf("bad: %v", checks) } for _, c := range checks { - if c.Type != "ttl" { - t.Fatalf("expected ttl check type, got %s", c.Type) + if c.Type != "ttl" && c.Type != "udp" { + t.Fatalf("expected ttl or udp check type, got %s", c.Type) } } @@ -3362,6 +3408,11 @@ func testAgent_RegisterService_ReRegister(t *testing.T, extraHCL string) { CheckID: types.CheckID("check_2"), TTL: 30 * time.Second, }, + { + CheckID: types.CheckID("check_3"), + UDP: "1.1.1.1", + Interval: 5 * time.Second, + }, }, Weights: &structs.Weights{ Passing: 100, @@ -3387,6 +3438,11 @@ func testAgent_RegisterService_ReRegister(t *testing.T, extraHCL string) { CheckID: types.CheckID("check_3"), TTL: 30 * time.Second, }, + { + CheckID: types.CheckID("check_3"), + UDP: "1.1.1.1", + Interval: 5 * time.Second, + }, }, Weights: &structs.Weights{ Passing: 100, @@ -3714,6 +3770,231 @@ func testAgent_RegisterService_TranslateKeys(t *testing.T, extraHCL string) { } } +func TestAgent_RegisterService_TranslateKeys_UDP(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Run("normal", func(t *testing.T) { + t.Parallel() + testAgent_RegisterService_TranslateKeys(t, "enable_central_service_config = false") + }) + t.Run("service manager", func(t *testing.T) { + t.Parallel() + testAgent_RegisterService_TranslateKeys(t, "enable_central_service_config = true") + }) +} + +func testAgent_RegisterService_TranslateKeys_UDP(t *testing.T, extraHCL string) { + t.Helper() + + tests := []struct { + ip string + expectedUDPCheckStart string + }{ + {"127.0.0.1", "127.0.0.1:"}, // private network address + {"::1", "[::1]:"}, // shared address space + } + for _, tt := range tests { + t.Run(tt.ip, func(t *testing.T) { + a := NewTestAgent(t, ` + connect {} +`+extraHCL) + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + json := ` + { + "name":"test", + "port":8000, + "enable_tag_override": true, + "tagged_addresses": { + "lan": { + "address": "1.2.3.4", + "port": 5353 + }, + "wan": { + "address": "2.3.4.5", + "port": 53 + } + }, + "meta": { + "some": "meta", + "enable_tag_override": "meta is 'opaque' so should not get translated" + }, + "kind": "connect-proxy",` + + // Note the uppercase P is important here - it ensures translation works + // correctly in case-insensitive way. Without it this test can pass even + // when translation is broken for other valid inputs. + `"Proxy": { + "destination_service_name": "web", + "destination_service_id": "web", + "local_service_port": 1234, + "local_service_address": "` + tt.ip + `", + "config": { + "destination_type": "proxy.config is 'opaque' so should not get translated" + }, + "upstreams": [ + { + "destination_type": "service", + "destination_namespace": "default", + "destination_partition": "default", + "destination_name": "db", + "local_bind_address": "` + tt.ip + `", + "local_bind_port": 1234, + "config": { + "destination_type": "proxy.upstreams.config is 'opaque' so should not get translated" + } + } + ] + }, + "connect": { + "sidecar_service": { + "name":"test-proxy", + "port":8001, + "enable_tag_override": true, + "meta": { + "some": "meta", + "enable_tag_override": "sidecar_service.meta is 'opaque' so should not get translated" + }, + "kind": "connect-proxy", + "proxy": { + "destination_service_name": "test", + "destination_service_id": "test", + "local_service_port": 4321, + "local_service_address": "` + tt.ip + `", + "upstreams": [ + { + "destination_type": "service", + "destination_namespace": "default", + "destination_partition": "default", + "destination_name": "db", + "local_bind_address": "` + tt.ip + `", + "local_bind_port": 1234, + "config": { + "destination_type": "sidecar_service.proxy.upstreams.config is 'opaque' so should not get translated" + } + } + ] + } + } + }, + "weights":{ + "passing": 16 + } + }` + req, _ := http.NewRequest("PUT", "/v1/agent/service/register", strings.NewReader(json)) + + rr := httptest.NewRecorder() + a.srv.h.ServeHTTP(rr, req) + require.Equal(t, 200, rr.Code, "body: %s", rr.Body) + + svc := &structs.NodeService{ + ID: "test", + Service: "test", + TaggedAddresses: map[string]structs.ServiceAddress{ + "lan": { + Address: "1.2.3.4", + Port: 5353, + }, + "wan": { + Address: "2.3.4.5", + Port: 53, + }, + }, + Meta: map[string]string{ + "some": "meta", + "enable_tag_override": "meta is 'opaque' so should not get translated", + }, + Port: 8000, + EnableTagOverride: true, + Weights: &structs.Weights{Passing: 16, Warning: 0}, + Kind: structs.ServiceKindConnectProxy, + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "web", + DestinationServiceID: "web", + LocalServiceAddress: tt.ip, + LocalServicePort: 1234, + Config: map[string]interface{}{ + "destination_type": "proxy.config is 'opaque' so should not get translated", + }, + Upstreams: structs.Upstreams{ + { + DestinationType: structs.UpstreamDestTypeService, + DestinationName: "db", + DestinationNamespace: "default", + DestinationPartition: "default", + LocalBindAddress: tt.ip, + LocalBindPort: 1234, + Config: map[string]interface{}{ + "destination_type": "proxy.upstreams.config is 'opaque' so should not get translated", + }, + }, + }, + }, + Connect: structs.ServiceConnect{ + // The sidecar service is nilled since it is only config sugar and + // shouldn't be represented in state. We assert that the translations + // there worked by inspecting the registered sidecar below. + SidecarService: nil, + }, + EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), + } + + got := a.State.Service(structs.NewServiceID("test", nil)) + require.Equal(t, svc, got) + + sidecarSvc := &structs.NodeService{ + Kind: structs.ServiceKindConnectProxy, + ID: "test-sidecar-proxy", + Service: "test-proxy", + Meta: map[string]string{ + "some": "meta", + "enable_tag_override": "sidecar_service.meta is 'opaque' so should not get translated", + }, + TaggedAddresses: map[string]structs.ServiceAddress{}, + Port: 8001, + EnableTagOverride: true, + Weights: &structs.Weights{Passing: 1, Warning: 1}, + LocallyRegisteredAsSidecar: true, + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "test", + DestinationServiceID: "test", + LocalServiceAddress: tt.ip, + LocalServicePort: 4321, + Upstreams: structs.Upstreams{ + { + DestinationType: structs.UpstreamDestTypeService, + DestinationName: "db", + DestinationNamespace: "default", + DestinationPartition: "default", + LocalBindAddress: tt.ip, + LocalBindPort: 1234, + Config: map[string]interface{}{ + "destination_type": "sidecar_service.proxy.upstreams.config is 'opaque' so should not get translated", + }, + }, + }, + }, + EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), + } + gotSidecar := a.State.Service(structs.NewServiceID("test-sidecar-proxy", nil)) + hasNoCorrectUDPCheck := true + for _, v := range a.checkUDPs { + if strings.HasPrefix(v.UDP, tt.expectedUDPCheckStart) { + hasNoCorrectUDPCheck = false + break + } + fmt.Println("UDP Check:= ", v) + } + if hasNoCorrectUDPCheck { + t.Fatalf("Did not find the expected UDP Healtcheck '%s' in %#v ", tt.expectedUDPCheckStart, a.checkUDPs) + } + require.Equal(t, sidecarSvc, gotSidecar) + }) + } +} + func TestAgent_RegisterService_ACLDeny(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") @@ -4463,6 +4744,503 @@ func testAgent_RegisterServiceDeregisterService_Sidecar(t *testing.T, extraHCL s } } +// This tests local agent service registration with a sidecar service. Note we +// only test simple defaults for the sidecar here since the actual logic for +// handling sidecar defaults and port assignment is tested thoroughly in +// TestAgent_sidecarServiceFromNodeService. Note it also tests Deregister +// explicitly too since setup is identical. +func TestAgent_RegisterServiceDeregisterService_Sidecar_UDP(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Run("normal", func(t *testing.T) { + t.Parallel() + testAgent_RegisterServiceDeregisterService_Sidecar_UDP(t, "enable_central_service_config = false") + }) + t.Run("service manager", func(t *testing.T) { + t.Parallel() + testAgent_RegisterServiceDeregisterService_Sidecar_UDP(t, "enable_central_service_config = true") + }) +} + +func testAgent_RegisterServiceDeregisterService_Sidecar_UDP(t *testing.T, extraHCL string) { + t.Helper() + + tests := []struct { + name string + preRegister, preRegister2 *structs.NodeService + // Use raw JSON payloads rather than encoding to avoid subtleties with some + // internal representations and different ways they encode and decode. We + // rely on the payload being Unmarshalable to structs.ServiceDefinition + // directly. + json string + enableACL bool + tokenRules string + wantNS *structs.NodeService + wantErr string + wantSidecarIDLeftAfterDereg bool + assertStateFn func(t *testing.T, state *local.State) + }{ + { + name: "sanity check no sidecar case", + json: ` + { + "name": "web", + "port": 1111 + } + `, + wantNS: nil, + wantErr: "", + }, + { + name: "default sidecar", + json: ` + { + "name": "web", + "port": 1111, + "connect": { + "SidecarService": {} + } + } + `, + wantNS: testDefaultSidecar("web", 1111), + wantErr: "", + }, + { + name: "ACL OK defaults", + json: ` + { + "name": "web", + "port": 1111, + "connect": { + "SidecarService": {} + } + } + `, + enableACL: true, + tokenRules: ` + service "web-sidecar-proxy" { + policy = "write" + } + service "web" { + policy = "write" + }`, + wantNS: testDefaultSidecar("web", 1111), + wantErr: "", + }, + { + name: "ACL denied", + json: ` + { + "name": "web", + "port": 1111, + "connect": { + "SidecarService": {} + } + } + `, + enableACL: true, + tokenRules: ``, // No token rules means no valid token + wantNS: nil, + wantErr: "Permission denied", + }, + { + name: "ACL OK for service but not for sidecar", + json: ` + { + "name": "web", + "port": 1111, + "connect": { + "SidecarService": {} + } + } + `, + enableACL: true, + // This will become more common/reasonable when ACLs support exact match. + tokenRules: ` + service "web-sidecar-proxy" { + policy = "deny" + } + service "web" { + policy = "write" + }`, + wantNS: nil, + wantErr: "Permission denied", + }, + { + name: "ACL OK for service and sidecar but not sidecar's overridden destination", + json: ` + { + "name": "web", + "port": 1111, + "connect": { + "SidecarService": { + "proxy": { + "DestinationServiceName": "foo" + } + } + } + } + `, + enableACL: true, + tokenRules: ` + service "web-sidecar-proxy" { + policy = "write" + } + service "web" { + policy = "write" + }`, + wantNS: nil, + wantErr: "Permission denied", + }, + { + name: "ACL OK for service but not for overridden sidecar", + json: ` + { + "name": "web", + "port": 1111, + "connect": { + "SidecarService": { + "name": "foo-sidecar-proxy" + } + } + } + `, + enableACL: true, + tokenRules: ` + service "web-sidecar-proxy" { + policy = "write" + } + service "web" { + policy = "write" + }`, + wantNS: nil, + wantErr: "Permission denied", + }, + { + name: "ACL OK for service but and overridden for sidecar", + // This test ensures that if the sidecar embeds it's own token with + // different privs from the main request token it will be honored for the + // sidecar registration. We use the test root token since that should have + // permission. + json: ` + { + "name": "web", + "port": 1111, + "connect": { + "SidecarService": { + "name": "foo", + "token": "root" + } + } + } + `, + enableACL: true, + tokenRules: ` + service "web-sidecar-proxy" { + policy = "write" + } + service "web" { + policy = "write" + }`, + wantNS: testDefaultSidecar("web", 1111, func(ns *structs.NodeService) { + ns.Service = "foo" + }), + wantErr: "", + }, + { + name: "invalid check definition in sidecar", + // Note no interval in the UDP check should fail validation + json: ` + { + "name": "web", + "port": 1111, + "connect": { + "SidecarService": { + "check": { + "UDP": "foo" + } + } + } + } + `, + wantNS: nil, + wantErr: "invalid check in sidecar_service", + }, + { + name: "invalid checks definitions in sidecar", + // Note no interval in the UDP check should fail validation + json: ` + { + "name": "web", + "port": 1111, + "connect": { + "SidecarService": { + "checks": [{ + "UDP": "foo" + }] + } + } + } + `, + wantNS: nil, + wantErr: "invalid check in sidecar_service", + }, + { + name: "invalid check status in sidecar", + // Note no interval in the UDP check should fail validation + json: ` + { + "name": "web", + "port": 1111, + "connect": { + "SidecarService": { + "check": { + "UDP": "foo", + "Interval": 10, + "Status": "unsupported-status" + } + } + } + } + `, + wantNS: nil, + wantErr: "Status for checks must 'passing', 'warning', 'critical'", + }, + { + name: "invalid checks status in sidecar", + // Note no interval in the UDP check should fail validation + json: ` + { + "name": "web", + "port": 1111, + "connect": { + "SidecarService": { + "checks": [{ + "UDP": "foo", + "Interval": 10, + "Status": "unsupported-status" + }] + } + } + } + `, + wantNS: nil, + wantErr: "Status for checks must 'passing', 'warning', 'critical'", + }, + { + name: "another service registered with same ID as a sidecar should not be deregistered", + // Add another service with the same ID that a sidecar for web would have + preRegister: &structs.NodeService{ + ID: "web-sidecar-proxy", + Service: "fake-sidecar", + Port: 9999, + }, + // Register web with NO SIDECAR + json: ` + { + "name": "web", + "port": 1111 + } + `, + // Note here that although the registration here didn't register it, we + // should still see the NodeService we pre-registered here. + wantNS: &structs.NodeService{ + ID: "web-sidecar-proxy", + Service: "fake-sidecar", + Port: 9999, + TaggedAddresses: map[string]structs.ServiceAddress{}, + Weights: &structs.Weights{ + Passing: 1, + Warning: 1, + }, + EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), + }, + // After we deregister the web service above, the fake sidecar with + // clashing ID SHOULD NOT have been removed since it wasn't part of the + // original registration. + wantSidecarIDLeftAfterDereg: true, + }, + { + name: "updates to sidecar should work", + // Add a valid sidecar already registered + preRegister: &structs.NodeService{ + ID: "web-sidecar-proxy", + Service: "web-sidecar-proxy", + LocallyRegisteredAsSidecar: true, + Port: 9999, + }, + // Register web with Sidecar on different port + json: ` + { + "name": "web", + "port": 1111, + "connect": { + "SidecarService": { + "Port": 6666 + } + } + } + `, + // Note here that although the registration here didn't register it, we + // should still see the NodeService we pre-registered here. + wantNS: &structs.NodeService{ + Kind: "connect-proxy", + ID: "web-sidecar-proxy", + Service: "web-sidecar-proxy", + LocallyRegisteredAsSidecar: true, + Port: 6666, + TaggedAddresses: map[string]structs.ServiceAddress{}, + Weights: &structs.Weights{ + Passing: 1, + Warning: 1, + }, + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "web", + DestinationServiceID: "web", + LocalServiceAddress: "127.0.0.1", + LocalServicePort: 1111, + }, + EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), + }, + }, + { + name: "update that removes sidecar should NOT deregister it", + // Add web with a valid sidecar already registered + preRegister: &structs.NodeService{ + ID: "web", + Service: "web", + Port: 1111, + }, + preRegister2: testDefaultSidecar("web", 1111), + // Register (update) web and remove sidecar (and port for sanity check) + json: ` + { + "name": "web", + "port": 2222 + } + `, + // Sidecar should still be there such that API can update registration + // without accidentally removing a sidecar. This is equivalent to embedded + // checks which are not removed by just not being included in an update. + // We will document that sidecar registrations via API must be explicitiy + // deregistered. + wantNS: testDefaultSidecar("web", 1111), + // Sanity check the rest of the update happened though. + assertStateFn: func(t *testing.T, state *local.State) { + svc := state.Service(structs.NewServiceID("web", nil)) + require.NotNil(t, svc) + require.Equal(t, 2222, svc.Port) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + // Constrain auto ports to 1 available to make it deterministic + hcl := `ports { + sidecar_min_port = 2222 + sidecar_max_port = 2222 + } + ` + if tt.enableACL { + hcl = hcl + TestACLConfig() + } + + a := NewTestAgent(t, hcl+" "+extraHCL) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + if tt.preRegister != nil { + require.NoError(t, a.addServiceFromSource(tt.preRegister, nil, false, "", ConfigSourceLocal)) + } + if tt.preRegister2 != nil { + require.NoError(t, a.addServiceFromSource(tt.preRegister2, nil, false, "", ConfigSourceLocal)) + } + + // Create an ACL token with require policy + var token string + if tt.enableACL && tt.tokenRules != "" { + token = testCreateToken(t, a, tt.tokenRules) + } + + br := bytes.NewBufferString(tt.json) + + req, _ := http.NewRequest("PUT", "/v1/agent/service/register?token="+token, br) + resp := httptest.NewRecorder() + a.srv.h.ServeHTTP(resp, req) + if tt.wantErr != "" { + require.Contains(t, strings.ToLower(resp.Body.String()), strings.ToLower(tt.wantErr)) + return + } + require.Equal(t, 200, resp.Code, "request failed with body: %s", + resp.Body.String()) + + // Sanity the target service registration + svcs := a.State.AllServices() + + // Parse the expected definition into a ServiceDefinition + var sd structs.ServiceDefinition + err := json.Unmarshal([]byte(tt.json), &sd) + require.NoError(t, err) + require.NotEmpty(t, sd.Name) + + svcID := sd.ID + if svcID == "" { + svcID = sd.Name + } + sid := structs.NewServiceID(svcID, nil) + svc, ok := svcs[sid] + require.True(t, ok, "has service "+sid.String()) + assert.Equal(t, sd.Name, svc.Service) + assert.Equal(t, sd.Port, svc.Port) + // Ensure that the actual registered service _doesn't_ still have it's + // sidecar info since it's duplicate and we don't want that synced up to + // the catalog or included in responses particularly - it's just + // registration syntax sugar. + assert.Nil(t, svc.Connect.SidecarService) + + if tt.wantNS == nil { + // Sanity check that there was no service registered, we rely on there + // being no services at start of test so we can just use the count. + assert.Len(t, svcs, 1, "should be no sidecar registered") + return + } + + // Ensure sidecar + svc, ok = svcs[structs.NewServiceID(tt.wantNS.ID, nil)] + require.True(t, ok, "no sidecar registered at "+tt.wantNS.ID) + assert.Equal(t, tt.wantNS, svc) + + if tt.assertStateFn != nil { + tt.assertStateFn(t, a.State) + } + + // Now verify deregistration also removes sidecar (if there was one and it + // was added via sidecar not just coincidental ID clash) + { + req := httptest.NewRequest("PUT", + "/v1/agent/service/deregister/"+svcID+"?token="+token, nil) + resp := httptest.NewRecorder() + a.srv.h.ServeHTTP(resp, req) + require.Equal(t, http.StatusOK, resp.Code) + + svcs := a.State.AllServices() + _, ok = svcs[structs.NewServiceID(tt.wantNS.ID, nil)] + if tt.wantSidecarIDLeftAfterDereg { + require.True(t, ok, "removed non-sidecar service at "+tt.wantNS.ID) + } else { + require.False(t, ok, "sidecar not deregistered with service "+svcID) + } + } + }) + } +} + +// END HERE + // This tests that connect proxy validation is done for local agent // registration. This doesn't need to test validation exhaustively since // that is done via a table test in the structs package. diff --git a/agent/catalog_endpoint_test.go b/agent/catalog_endpoint_test.go index ff45a10cb..eb1b509e0 100644 --- a/agent/catalog_endpoint_test.go +++ b/agent/catalog_endpoint_test.go @@ -603,6 +603,63 @@ func TestCatalogRegister_checkRegistration(t *testing.T) { }) } +func TestCatalogRegister_checkRegistration_UDP(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + defer a.Shutdown() + + // Register node with a service and check + check := structs.HealthCheck{ + Node: "foo", + CheckID: "foo-check", + Name: "foo check", + ServiceID: "api", + Definition: structs.HealthCheckDefinition{ + UDP: "localhost:8888", + Interval: 5 * time.Second, + }, + } + + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "api", + }, + Check: &check, + } + + var out struct{} + if err := a.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + retry.Run(t, func(r *retry.R) { + req, _ := http.NewRequest("GET", "/v1/health/checks/api", nil) + resp := httptest.NewRecorder() + obj, err := a.srv.HealthServiceChecks(resp, req) + if err != nil { + r.Fatalf("err: %v", err) + } + + checks := obj.(structs.HealthChecks) + if len(checks) != 1 { + r.Fatalf("expected 1 check, got: %d", len(checks)) + } + if checks[0].CheckID != check.CheckID { + r.Fatalf("expected check id %s, got %s", check.Type, checks[0].Type) + } + if checks[0].Type != "udp" { + r.Fatalf("expected check type udp, got %s", checks[0].Type) + } + }) +} + func TestCatalogServiceNodes(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") diff --git a/agent/checks/check.go b/agent/checks/check.go index 3e3ce44f8..3712b31b8 100644 --- a/agent/checks/check.go +++ b/agent/checks/check.go @@ -1,6 +1,7 @@ package checks import ( + "bufio" "context" "crypto/tls" "fmt" @@ -703,6 +704,135 @@ func (c *CheckTCP) check() { c.StatusHandler.updateCheck(c.CheckID, api.HealthPassing, fmt.Sprintf("TCP connect %s: Success", c.TCP)) } +// CheckUDP is used to periodically send a UDP datagram to determine the health of a given check. +// The check is passing if the connection succeeds, the response is bytes.Equal to the bytes passed +// in or if the error returned is a timeout error +// The check is critical if: the connection succeeds but the response is not equal to the bytes passed in, +// the connection succeeds but the error returned is not a timeout error or the connection fails +type CheckUDP struct { + CheckID structs.CheckID + ServiceID structs.ServiceID + UDP string + Message string + Interval time.Duration + Timeout time.Duration + Logger hclog.Logger + StatusHandler *StatusHandler + + dialer *net.Dialer + stop bool + stopCh chan struct{} + stopLock sync.Mutex +} + +func (c *CheckUDP) Start() { + c.stopLock.Lock() + defer c.stopLock.Unlock() + + if c.dialer == nil { + // Create the socket dialer + c.dialer = &net.Dialer{ + Timeout: 10 * time.Second, + } + if c.Timeout > 0 { + c.dialer.Timeout = c.Timeout + } + } + + c.stop = false + c.stopCh = make(chan struct{}) + go c.run() +} + +func (c *CheckUDP) Stop() { + c.stopLock.Lock() + defer c.stopLock.Unlock() + if !c.stop { + c.stop = true + close(c.stopCh) + } +} + +func (c *CheckUDP) run() { + // Get the randomized initial pause time + initialPauseTime := lib.RandomStagger(c.Interval) + next := time.After(initialPauseTime) + for { + select { + case <-next: + c.check() + next = time.After(c.Interval) + case <-c.stopCh: + return + } + } + +} + +func (c *CheckUDP) check() { + + conn, err := c.dialer.Dial(`udp`, c.UDP) + + if err != nil { + if e, ok := err.(net.Error); ok && e.Timeout() { + c.StatusHandler.updateCheck(c.CheckID, api.HealthPassing, fmt.Sprintf("UDP connect %s: Success", c.UDP)) + return + } else { + c.Logger.Warn("Check socket connection failed", + "check", c.CheckID.String(), + "error", err, + ) + c.StatusHandler.updateCheck(c.CheckID, api.HealthCritical, err.Error()) + return + } + } + defer conn.Close() + + n, err := fmt.Fprintf(conn, c.Message) + if err != nil { + c.Logger.Warn("Check socket write failed", + "check", c.CheckID.String(), + "error", err, + ) + c.StatusHandler.updateCheck(c.CheckID, api.HealthCritical, err.Error()) + return + } + + if n != len(c.Message) { + c.Logger.Warn("Check socket short write", + "check", c.CheckID.String(), + "error", err, + ) + c.StatusHandler.updateCheck(c.CheckID, api.HealthCritical, err.Error()) + return + } + + if err != nil { + c.Logger.Warn("Check socket write failed", + "check", c.CheckID.String(), + "error", err, + ) + c.StatusHandler.updateCheck(c.CheckID, api.HealthCritical, err.Error()) + return + } + _, err = bufio.NewReader(conn).Read(make([]byte, 1)) + if err != nil { + if strings.Contains(err.Error(), "i/o timeout") { + c.StatusHandler.updateCheck(c.CheckID, api.HealthPassing, fmt.Sprintf("UDP connect %s: Success", c.UDP)) + return + } else { + c.Logger.Warn("Check socket read failed", + "check", c.CheckID.String(), + "error", err, + ) + c.StatusHandler.updateCheck(c.CheckID, api.HealthCritical, err.Error()) + return + } + } else if err == nil { + c.StatusHandler.updateCheck(c.CheckID, api.HealthPassing, fmt.Sprintf("UDP connect %s: Success", c.UDP)) + } +} + // CheckDocker is used to periodically invoke a script to // determine the health of an application running inside a // Docker Container. We assume that the script is compatible diff --git a/agent/checks/check_test.go b/agent/checks/check_test.go index caddee424..b8f16d890 100644 --- a/agent/checks/check_test.go +++ b/agent/checks/check_test.go @@ -2,20 +2,25 @@ package checks import ( "bytes" + "context" "fmt" + "log" "net" "net/http" "net/http/httptest" "os" "reflect" "regexp" + "strconv" "strings" + "sync" "testing" "time" "github.com/hashicorp/consul/agent/mock" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-uuid" @@ -1141,6 +1146,152 @@ func TestCheckTCPPassing(t *testing.T) { tcpServer.Close() } +func sendResponse(conn *net.UDPConn, addr *net.UDPAddr) { + _, err := conn.WriteToUDP([]byte("healthy"), addr) + if err != nil { + fmt.Printf("Couldn't send response %v", err) + } +} + +func mockUDPServer(ctx context.Context, network string, port int) { + + b := make([]byte, 1024) + addr := fmt.Sprintf(`127.0.0.1:%d`, port) + + udpAddr, err := net.ResolveUDPAddr(network, addr) + if err != nil { + log.Fatal("Error resolving UDP address: ", err) + } + + ser, err := net.ListenUDP("udp", udpAddr) + if err != nil { + log.Fatal("Error listening UDP: ", err) + } + defer ser.Close() + + chClose := make(chan interface{}) + wg := sync.WaitGroup{} + wg.Add(1) + + go func() { + for { + log.Print("Waiting for UDP message") + _, remoteaddr, err := ser.ReadFromUDP(b) + log.Printf("Read a message from %v %s \n", remoteaddr, b) + if err != nil { + log.Fatalf("Error reading from UDP %s", err.Error()) + } + sendResponse(ser, remoteaddr) + select { + case <-chClose: + fmt.Println("cancelled") + wg.Done() + return + default: + } + } + }() + + select { + case <-ctx.Done(): + fmt.Println("cancelled") + close(chClose) + } + wg.Wait() + return +} + +func expectUDPStatus(t *testing.T, udp string, status string) { + notif := mock.NewNotify() + logger := testutil.Logger(t) + statusHandler := NewStatusHandler(notif, logger, 0, 0, 0) + cid := structs.NewCheckID("foo", nil) + + check := &CheckUDP{ + CheckID: cid, + UDP: udp, + Interval: 10 * time.Millisecond, + Logger: logger, + StatusHandler: statusHandler, + } + check.Start() + defer check.Stop() + retry.Run(t, func(r *retry.R) { + if got, want := notif.Updates(cid), 2; got < want { + r.Fatalf("got %d updates want at least %d", got, want) + } + if got, want := notif.State(cid), status; got != want { + r.Fatalf("got state %q want %q", got, want) + } + }) +} + +func expectUDPTimeout(t *testing.T, udp string, status string) { + notif := mock.NewNotify() + logger := testutil.Logger(t) + statusHandler := NewStatusHandler(notif, logger, 0, 0, 0) + cid := structs.NewCheckID("foo", nil) + + check := &CheckUDP{ + CheckID: cid, + UDP: udp, + Interval: 10 * time.Millisecond, + Timeout: 5 * time.Nanosecond, + Logger: logger, + StatusHandler: statusHandler, + } + check.Start() + defer check.Stop() + retry.Run(t, func(r *retry.R) { + if got, want := notif.Updates(cid), 2; got < want { + r.Fatalf("got %d updates want at least %d", got, want) + } + if got, want := notif.State(cid), status; got != want { + r.Fatalf("got state %q want %q", got, want) + } + }) +} + +func TestCheckUDPTimeoutPassing(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + port := freeport.GetOne(t) + serverUrl := "127.0.0.1:" + strconv.Itoa(port) + + go mockUDPServer(ctx, `udp`, port) + expectUDPTimeout(t, serverUrl, api.HealthPassing) // Should pass since timeout is handled as success, from specification +} +func TestCheckUDPCritical(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + port := freeport.GetOne(t) + notExistentPort := freeport.GetOne(t) + serverUrl := "127.0.0.1:" + strconv.Itoa(notExistentPort) + + go mockUDPServer(ctx, `udp`, port) + + expectUDPStatus(t, serverUrl, api.HealthCritical) // Should be unhealthy since we never connect to mocked udp server. +} + +func TestCheckUDPPassing(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + port := freeport.GetOne(t) + serverUrl := "127.0.0.1:" + strconv.Itoa(port) + + go mockUDPServer(ctx, `udp`, port) + expectUDPStatus(t, serverUrl, api.HealthPassing) +} + func TestCheckH2PING(t *testing.T) { t.Parallel() diff --git a/agent/config/config.go b/agent/config/config.go index 0bb16cda5..dbca8e1cf 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -403,6 +403,7 @@ type CheckDefinition struct { DisableRedirects *bool `mapstructure:"disable_redirects"` OutputMaxSize *int `mapstructure:"output_max_size"` TCP *string `mapstructure:"tcp"` + UDP *string `mapstructure:"udp"` Interval *string `mapstructure:"interval"` DockerContainerID *string `mapstructure:"docker_container_id" alias:"dockercontainerid"` Shell *string `mapstructure:"shell"` diff --git a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden index e49398b8d..090a7191a 100644 --- a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden +++ b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden @@ -118,7 +118,8 @@ "TLSSkipVerify": false, "TTL": "0s", "Timeout": "0s", - "Token": "hidden" + "Token": "hidden", + "UDP": "" } ], "ClientAddrs": [], @@ -324,7 +325,8 @@ "TLSServerName": "", "TLSSkipVerify": false, "TTL": "0s", - "Timeout": "0s" + "Timeout": "0s", + "UDP": "" }, "Checks": [], "Connect": null, diff --git a/agent/structs/check_definition.go b/agent/structs/check_definition.go index c991c993c..5f204ebf6 100644 --- a/agent/structs/check_definition.go +++ b/agent/structs/check_definition.go @@ -33,6 +33,7 @@ type CheckDefinition struct { Body string DisableRedirects bool TCP string + UDP string Interval time.Duration DockerContainerID string Shell string @@ -215,6 +216,7 @@ func (c *CheckDefinition) CheckType() *CheckType { DisableRedirects: c.DisableRedirects, OutputMaxSize: c.OutputMaxSize, TCP: c.TCP, + UDP: c.UDP, Interval: c.Interval, DockerContainerID: c.DockerContainerID, Shell: c.Shell, diff --git a/agent/structs/check_type.go b/agent/structs/check_type.go index 0c89a00f2..797895503 100644 --- a/agent/structs/check_type.go +++ b/agent/structs/check_type.go @@ -39,6 +39,7 @@ type CheckType struct { Body string DisableRedirects bool TCP string + UDP string Interval time.Duration AliasNode string AliasService string @@ -179,13 +180,13 @@ func (t *CheckType) UnmarshalJSON(data []byte) (err error) { // Validate returns an error message if the check is invalid func (c *CheckType) Validate() error { - intervalCheck := c.IsScript() || c.HTTP != "" || c.TCP != "" || c.GRPC != "" || c.H2PING != "" + intervalCheck := c.IsScript() || c.HTTP != "" || c.TCP != "" || c.UDP != "" || c.GRPC != "" || c.H2PING != "" if c.Interval > 0 && c.TTL > 0 { return fmt.Errorf("Interval and TTL cannot both be specified") } if intervalCheck && c.Interval <= 0 { - return fmt.Errorf("Interval must be > 0 for Script, HTTP, H2PING, or TCP checks") + return fmt.Errorf("Interval must be > 0 for Script, HTTP, H2PING, TCP or UDP checks") } if intervalCheck && c.IsAlias() { return fmt.Errorf("Interval cannot be set for Alias checks") @@ -241,6 +242,10 @@ func (c *CheckType) IsTCP() bool { return c.TCP != "" && c.Interval > 0 } +func (c *CheckType) IsUDP() bool { + return c.UDP != "" && c.Interval > 0 +} + // IsDocker returns true when checking a docker container. func (c *CheckType) IsDocker() bool { return c.IsScript() && c.DockerContainerID != "" && c.Interval > 0 @@ -266,6 +271,8 @@ func (c *CheckType) Type() string { return "ttl" case c.IsTCP(): return "tcp" + case c.IsUDP(): + return "udp" case c.IsAlias(): return "alias" case c.IsDocker(): diff --git a/agent/structs/structs.go b/agent/structs/structs.go index d53644ed7..94c425610 100644 --- a/agent/structs/structs.go +++ b/agent/structs/structs.go @@ -1670,7 +1670,7 @@ type HealthCheck struct { ServiceID string // optional associated service ServiceName string // optional service name ServiceTags []string // optional service tags - Type string // Check type: http/ttl/tcp/etc + Type string // Check type: http/ttl/tcp/udp/etc Interval string // from definition Timeout string // from definition @@ -1735,6 +1735,7 @@ type HealthCheckDefinition struct { Body string `json:",omitempty"` DisableRedirects bool `json:",omitempty"` TCP string `json:",omitempty"` + UDP string `json:",omitempty"` H2PING string `json:",omitempty"` H2PingUseTLS bool `json:",omitempty"` Interval time.Duration `json:",omitempty"` @@ -1885,6 +1886,7 @@ func (c *HealthCheck) CheckType() *CheckType { Body: c.Definition.Body, DisableRedirects: c.Definition.DisableRedirects, TCP: c.Definition.TCP, + UDP: c.Definition.UDP, H2PING: c.Definition.H2PING, H2PingUseTLS: c.Definition.H2PingUseTLS, Interval: c.Definition.Interval, diff --git a/api/agent.go b/api/agent.go index 724767556..61e829a64 100644 --- a/api/agent.go +++ b/api/agent.go @@ -333,6 +333,7 @@ type AgentServiceCheck struct { Method string `json:",omitempty"` Body string `json:",omitempty"` TCP string `json:",omitempty"` + UDP string `json:",omitempty"` Status string `json:",omitempty"` Notes string `json:",omitempty"` TLSServerName string `json:",omitempty"` diff --git a/api/health.go b/api/health.go index e70861c8a..2bcb3cb52 100644 --- a/api/health.go +++ b/api/health.go @@ -62,6 +62,7 @@ type HealthCheckDefinition struct { TLSServerName string TLSSkipVerify bool TCP string + UDP string GRPC string GRPCUseTLS bool IntervalDuration time.Duration `json:"-"` diff --git a/api/txn_test.go b/api/txn_test.go index cab3b4ff5..bf69b7bc8 100644 --- a/api/txn_test.go +++ b/api/txn_test.go @@ -56,6 +56,28 @@ func TestAPI_ClientTxn(t *testing.T) { DeregisterCriticalServiceAfter: ReadableDuration(160 * time.Second), }, }, + { + CheckID: "bor", + Status: "critical", + Definition: HealthCheckDefinition{ + UDP: "1.1.1.1", + Interval: ReadableDuration(5 * time.Second), + Timeout: ReadableDuration(10 * time.Second), + DeregisterCriticalServiceAfter: ReadableDuration(20 * time.Second), + }, + Type: "udp", + }, + { + CheckID: "bur", + Status: "passing", + Definition: HealthCheckDefinition{ + UDP: "2.2.2.2", + Interval: ReadableDuration(5 * time.Second), + Timeout: ReadableDuration(10 * time.Second), + DeregisterCriticalServiceAfter: ReadableDuration(20 * time.Second), + }, + Type: "udp", + }, }, } _, err = catalog.Register(reg, nil) @@ -115,6 +137,18 @@ func TestAPI_ClientTxn(t *testing.T) { Check: HealthCheck{Node: "foo", CheckID: "baz"}, }, }, + &TxnOp{ + Check: &CheckTxnOp{ + Verb: CheckGet, + Check: HealthCheck{Node: "foo", CheckID: "bor"}, + }, + }, + &TxnOp{ + Check: &CheckTxnOp{ + Verb: CheckGet, + Check: HealthCheck{Node: "foo", CheckID: "bur"}, + }, + }, } ok, ret, _, err := txn.Txn(ops, nil) if err != nil { @@ -141,7 +175,7 @@ func TestAPI_ClientTxn(t *testing.T) { t.Fatalf("transaction failure") } - if ret == nil || len(ret.Errors) != 0 || len(ret.Results) != 6 { + if ret == nil || len(ret.Errors) != 0 || len(ret.Results) != 8 { t.Fatalf("bad: %v", ret) } expected := TxnResults{ @@ -230,6 +264,48 @@ func TestAPI_ClientTxn(t *testing.T) { ModifyIndex: ret.Results[4].Check.CreateIndex, }, }, + &TxnResult{ + Check: &HealthCheck{ + Node: "foo", + CheckID: "bor", + Status: "critical", + Definition: HealthCheckDefinition{ + UDP: "1.1.1.1", + Interval: ReadableDuration(5 * time.Second), + IntervalDuration: 5 * time.Second, + Timeout: ReadableDuration(10 * time.Second), + TimeoutDuration: 10 * time.Second, + DeregisterCriticalServiceAfter: ReadableDuration(20 * time.Second), + DeregisterCriticalServiceAfterDuration: 20 * time.Second, + }, + Type: "udp", + Partition: splitDefaultPartition, + Namespace: splitDefaultNamespace, + CreateIndex: ret.Results[4].Check.CreateIndex, + ModifyIndex: ret.Results[4].Check.CreateIndex, + }, + }, + &TxnResult{ + Check: &HealthCheck{ + Node: "foo", + CheckID: "bur", + Status: "passing", + Definition: HealthCheckDefinition{ + UDP: "2.2.2.2", + Interval: ReadableDuration(5 * time.Second), + IntervalDuration: 5 * time.Second, + Timeout: ReadableDuration(10 * time.Second), + TimeoutDuration: 10 * time.Second, + DeregisterCriticalServiceAfter: ReadableDuration(20 * time.Second), + DeregisterCriticalServiceAfterDuration: 20 * time.Second, + }, + Type: "udp", + Partition: splitDefaultPartition, + Namespace: splitDefaultNamespace, + CreateIndex: ret.Results[4].Check.CreateIndex, + ModifyIndex: ret.Results[4].Check.CreateIndex, + }, + }, } require.Equal(t, expected, ret.Results) diff --git a/build-support/scripts/devtools.sh b/build-support/scripts/devtools.sh index 32aea96bd..1d2078abc 100755 --- a/build-support/scripts/devtools.sh +++ b/build-support/scripts/devtools.sh @@ -149,10 +149,10 @@ function install_unversioned_tool { local install="$2" if ! command -v "${command}" &>/dev/null ; then - status_stage "installing tool: ${install}" + echo "installing tool: ${install}" go install "${install}" else - debug "skipping tool: ${install} (installed)" + echo "skipping tool: ${install} (installed)" fi return 0 @@ -183,7 +183,7 @@ function install_versioned_tool { err "dev version of '${command}' requested but not installed" return 1 fi - status "skipping tool: ${installbase} (using development version)" + echo "skipping tool: ${installbase} (using development version)" return 0 fi @@ -210,10 +210,10 @@ function install_versioned_tool { fi if [[ -n $should_install ]]; then - status_stage "installing tool (${install_reason}): ${install}" + echo "installing tool (${install_reason}): ${install}" go install "${install}" else - debug "skipping tool: ${install} (installed)" + echo "skipping tool: ${install} (installed)" fi return 0 } diff --git a/proto/pbservice/healthcheck.gen.go b/proto/pbservice/healthcheck.gen.go index 4eef24bef..cfaadc2b4 100644 --- a/proto/pbservice/healthcheck.gen.go +++ b/proto/pbservice/healthcheck.gen.go @@ -21,6 +21,7 @@ func CheckTypeToStructs(s *CheckType, t *structs.CheckType) { t.Body = s.Body t.DisableRedirects = s.DisableRedirects t.TCP = s.TCP + t.UDP = s.UDP t.Interval = structs.DurationFromProto(s.Interval) t.AliasNode = s.AliasNode t.AliasService = s.AliasService @@ -57,6 +58,7 @@ func CheckTypeFromStructs(t *structs.CheckType, s *CheckType) { s.Body = t.Body s.DisableRedirects = t.DisableRedirects s.TCP = t.TCP + s.UDP = t.UDP s.Interval = structs.DurationToProto(t.Interval) s.AliasNode = t.AliasNode s.AliasService = t.AliasService @@ -138,6 +140,7 @@ func HealthCheckDefinitionToStructs(s *HealthCheckDefinition, t *structs.HealthC t.Body = s.Body t.DisableRedirects = s.DisableRedirects t.TCP = s.TCP + t.UDP = s.UDP t.H2PING = s.H2PING t.H2PingUseTLS = s.H2PingUseTLS t.Interval = structs.DurationFromProto(s.Interval) @@ -165,6 +168,7 @@ func HealthCheckDefinitionFromStructs(t *structs.HealthCheckDefinition, s *Healt s.Body = t.Body s.DisableRedirects = t.DisableRedirects s.TCP = t.TCP + s.UDP = t.UDP s.H2PING = t.H2PING s.H2PingUseTLS = t.H2PingUseTLS s.Interval = structs.DurationToProto(t.Interval) diff --git a/proto/pbservice/healthcheck.pb.go b/proto/pbservice/healthcheck.pb.go index 317f7f6db..964ee2257 100644 --- a/proto/pbservice/healthcheck.pb.go +++ b/proto/pbservice/healthcheck.pb.go @@ -276,6 +276,7 @@ type HealthCheckDefinition struct { Body string `protobuf:"bytes,18,opt,name=Body,proto3" json:"Body,omitempty"` DisableRedirects bool `protobuf:"varint,22,opt,name=DisableRedirects,proto3" json:"DisableRedirects,omitempty"` TCP string `protobuf:"bytes,5,opt,name=TCP,proto3" json:"TCP,omitempty"` + UDP string `protobuf:"bytes,23,opt,name=UDP,proto3" json:"UDP,omitempty"` // mog: func-to=structs.DurationFromProto func-from=structs.DurationToProto Interval *durationpb.Duration `protobuf:"bytes,6,opt,name=Interval,proto3" json:"Interval,omitempty"` // mog: func-to=uint func-from=uint32 @@ -385,6 +386,13 @@ func (x *HealthCheckDefinition) GetTCP() string { return "" } +func (x *HealthCheckDefinition) GetUDP() string { + if x != nil { + return x.UDP + } + return "" +} + func (x *HealthCheckDefinition) GetInterval() *durationpb.Duration { if x != nil { return x.Interval @@ -513,6 +521,7 @@ type CheckType struct { Body string `protobuf:"bytes,26,opt,name=Body,proto3" json:"Body,omitempty"` DisableRedirects bool `protobuf:"varint,31,opt,name=DisableRedirects,proto3" json:"DisableRedirects,omitempty"` TCP string `protobuf:"bytes,8,opt,name=TCP,proto3" json:"TCP,omitempty"` + UDP string `protobuf:"bytes,32,opt,name=UDP,proto3" json:"UDP,omitempty"` // mog: func-to=structs.DurationFromProto func-from=structs.DurationToProto Interval *durationpb.Duration `protobuf:"bytes,9,opt,name=Interval,proto3" json:"Interval,omitempty"` AliasNode string `protobuf:"bytes,10,opt,name=AliasNode,proto3" json:"AliasNode,omitempty"` @@ -656,6 +665,13 @@ func (x *CheckType) GetTCP() string { return "" } +func (x *CheckType) GetUDP() string { + if x != nil { + return x.UDP + } + return "" +} + func (x *CheckType) GetInterval() *durationpb.Duration { if x != nil { return x.Interval @@ -843,7 +859,7 @@ var file_proto_pbservice_healthcheck_proto_rawDesc = []byte{ 0x28, 0x09, 0x52, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x23, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x22, 0xae, 0x07, 0x0a, 0x15, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, + 0x65, 0x22, 0xc0, 0x07, 0x0a, 0x15, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x48, 0x54, 0x54, 0x50, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x4c, 0x53, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, @@ -861,134 +877,136 @@ var file_proto_pbservice_healthcheck_proto_rawDesc = []byte{ 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x54, 0x43, 0x50, 0x12, 0x35, 0x0a, 0x08, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x12, 0x24, 0x0a, 0x0d, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4d, 0x61, 0x78, 0x53, 0x69, 0x7a, - 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4d, - 0x61, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, - 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x07, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x61, 0x0a, 0x1e, 0x44, - 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, - 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x54, 0x43, 0x50, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x44, 0x50, + 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x44, 0x50, 0x12, 0x35, 0x0a, 0x08, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, + 0x61, 0x6c, 0x12, 0x24, 0x0a, 0x0d, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4d, 0x61, 0x78, 0x53, + 0x69, 0x7a, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x4d, 0x61, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x54, 0x69, 0x6d, 0x65, + 0x6f, 0x75, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x61, 0x0a, + 0x1e, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x43, 0x72, 0x69, 0x74, 0x69, + 0x63, 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x1e, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x43, 0x72, 0x69, 0x74, + 0x69, 0x63, 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, + 0x12, 0x1e, 0x0a, 0x0a, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x72, 0x67, 0x73, 0x18, 0x0a, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x72, 0x67, 0x73, + 0x12, 0x2c, 0x0a, 0x11, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x49, 0x44, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x44, 0x6f, 0x63, + 0x6b, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x44, 0x12, 0x14, + 0x0a, 0x05, 0x53, 0x68, 0x65, 0x6c, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x53, + 0x68, 0x65, 0x6c, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x48, 0x32, 0x50, 0x49, 0x4e, 0x47, 0x18, 0x14, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x48, 0x32, 0x50, 0x49, 0x4e, 0x47, 0x12, 0x22, 0x0a, 0x0c, + 0x48, 0x32, 0x50, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x65, 0x54, 0x4c, 0x53, 0x18, 0x15, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0c, 0x48, 0x32, 0x50, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x65, 0x54, 0x4c, 0x53, + 0x12, 0x12, 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x47, 0x52, 0x50, 0x43, 0x12, 0x1e, 0x0a, 0x0a, 0x47, 0x52, 0x50, 0x43, 0x55, 0x73, 0x65, 0x54, + 0x4c, 0x53, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x47, 0x52, 0x50, 0x43, 0x55, 0x73, + 0x65, 0x54, 0x4c, 0x53, 0x12, 0x1c, 0x0a, 0x09, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4e, 0x6f, 0x64, + 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4e, 0x6f, + 0x64, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x1e, - 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, - 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x1e, - 0x0a, 0x0a, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x72, 0x67, 0x73, 0x18, 0x0a, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0a, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x72, 0x67, 0x73, 0x12, 0x2c, - 0x0a, 0x11, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x49, 0x44, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x44, 0x6f, 0x63, 0x6b, 0x65, - 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, - 0x53, 0x68, 0x65, 0x6c, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x53, 0x68, 0x65, - 0x6c, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x48, 0x32, 0x50, 0x49, 0x4e, 0x47, 0x18, 0x14, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x48, 0x32, 0x50, 0x49, 0x4e, 0x47, 0x12, 0x22, 0x0a, 0x0c, 0x48, 0x32, - 0x50, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x65, 0x54, 0x4c, 0x53, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0c, 0x48, 0x32, 0x50, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x65, 0x54, 0x4c, 0x53, 0x12, 0x12, - 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x47, 0x52, - 0x50, 0x43, 0x12, 0x1e, 0x0a, 0x0a, 0x47, 0x52, 0x50, 0x43, 0x55, 0x73, 0x65, 0x54, 0x4c, 0x53, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x47, 0x52, 0x50, 0x43, 0x55, 0x73, 0x65, 0x54, - 0x4c, 0x53, 0x12, 0x1c, 0x0a, 0x09, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4e, 0x6f, 0x64, 0x65, 0x18, - 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4e, 0x6f, 0x64, 0x65, - 0x12, 0x22, 0x0a, 0x0c, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x11, 0x20, 0x01, 0x28, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, + 0x54, 0x54, 0x4c, 0x1a, 0x4f, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x48, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0xe2, 0x09, 0x0a, 0x09, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x44, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, + 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x6f, 0x74, 0x65, + 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x1e, + 0x0a, 0x0a, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x72, 0x67, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0a, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x72, 0x67, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x48, 0x54, + 0x54, 0x50, 0x12, 0x36, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x14, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x2a, 0x0a, 0x10, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, + 0x65, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x73, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x10, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x54, 0x43, 0x50, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x18, 0x20, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x55, 0x44, 0x50, 0x12, 0x35, 0x0a, 0x08, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, + 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, + 0x09, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4e, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x41, + 0x6c, 0x69, 0x61, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x2c, 0x0a, 0x11, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x49, 0x44, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x44, 0x6f, 0x63, 0x6b, + 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x44, 0x12, 0x14, 0x0a, + 0x05, 0x53, 0x68, 0x65, 0x6c, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x53, 0x68, + 0x65, 0x6c, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x48, 0x32, 0x50, 0x49, 0x4e, 0x47, 0x18, 0x1c, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x48, 0x32, 0x50, 0x49, 0x4e, 0x47, 0x12, 0x22, 0x0a, 0x0c, 0x48, + 0x32, 0x50, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x65, 0x54, 0x4c, 0x53, 0x18, 0x1e, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0c, 0x48, 0x32, 0x50, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x65, 0x54, 0x4c, 0x53, 0x12, + 0x12, 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x47, + 0x52, 0x50, 0x43, 0x12, 0x1e, 0x0a, 0x0a, 0x47, 0x52, 0x50, 0x43, 0x55, 0x73, 0x65, 0x54, 0x4c, + 0x53, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x47, 0x52, 0x50, 0x43, 0x55, 0x73, 0x65, + 0x54, 0x4c, 0x53, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x4c, 0x53, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x4c, 0x53, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x4c, 0x53, + 0x53, 0x6b, 0x69, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0d, 0x54, 0x4c, 0x53, 0x53, 0x6b, 0x69, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, + 0x33, 0x0a, 0x07, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x54, 0x69, 0x6d, + 0x65, 0x6f, 0x75, 0x74, 0x12, 0x2b, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x54, 0x54, - 0x4c, 0x1a, 0x4f, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x22, 0xd0, 0x09, 0x0a, 0x09, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x18, 0x0a, 0x07, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, - 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x72, 0x67, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x0a, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x72, 0x67, 0x73, 0x12, 0x12, 0x0a, 0x04, - 0x48, 0x54, 0x54, 0x50, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x48, 0x54, 0x54, 0x50, - 0x12, 0x36, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x54, 0x79, 0x70, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x12, 0x12, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x42, 0x6f, 0x64, 0x79, 0x12, 0x2a, 0x0a, 0x10, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, - 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x73, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, - 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x73, - 0x12, 0x10, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x54, - 0x43, 0x50, 0x12, 0x35, 0x0a, 0x08, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x08, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x41, 0x6c, 0x69, - 0x61, 0x73, 0x4e, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x41, 0x6c, - 0x69, 0x61, 0x73, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x41, 0x6c, 0x69, 0x61, 0x73, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x41, - 0x6c, 0x69, 0x61, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x44, - 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x44, - 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x43, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x53, 0x68, 0x65, - 0x6c, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x53, 0x68, 0x65, 0x6c, 0x6c, 0x12, - 0x16, 0x0a, 0x06, 0x48, 0x32, 0x50, 0x49, 0x4e, 0x47, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x48, 0x32, 0x50, 0x49, 0x4e, 0x47, 0x12, 0x22, 0x0a, 0x0c, 0x48, 0x32, 0x50, 0x69, 0x6e, - 0x67, 0x55, 0x73, 0x65, 0x54, 0x4c, 0x53, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x48, - 0x32, 0x50, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x65, 0x54, 0x4c, 0x53, 0x12, 0x12, 0x0a, 0x04, 0x47, - 0x52, 0x50, 0x43, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x47, 0x52, 0x50, 0x43, 0x12, - 0x1e, 0x0a, 0x0a, 0x47, 0x52, 0x50, 0x43, 0x55, 0x73, 0x65, 0x54, 0x4c, 0x53, 0x18, 0x0f, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0a, 0x47, 0x52, 0x50, 0x43, 0x55, 0x73, 0x65, 0x54, 0x4c, 0x53, 0x12, - 0x24, 0x0a, 0x0d, 0x54, 0x4c, 0x53, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, - 0x18, 0x1b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x4c, 0x53, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x4c, 0x53, 0x53, 0x6b, 0x69, 0x70, - 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x54, 0x4c, - 0x53, 0x53, 0x6b, 0x69, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x33, 0x0a, 0x07, 0x54, - 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, - 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x12, 0x2b, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x32, 0x0a, + 0x4c, 0x12, 0x32, 0x0a, 0x14, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x42, 0x65, 0x66, 0x6f, + 0x72, 0x65, 0x50, 0x61, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x15, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x50, 0x61, - 0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x15, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x53, 0x75, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x50, 0x61, 0x73, 0x73, 0x69, 0x6e, - 0x67, 0x12, 0x34, 0x0a, 0x15, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x42, 0x65, 0x66, - 0x6f, 0x72, 0x65, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x15, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, - 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x36, 0x0a, 0x16, 0x46, 0x61, 0x69, 0x6c, 0x75, - 0x72, 0x65, 0x73, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, - 0x6c, 0x18, 0x16, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, - 0x73, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x12, - 0x1c, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x48, 0x54, 0x54, 0x50, 0x18, 0x17, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x48, 0x54, 0x54, 0x50, 0x12, 0x1c, 0x0a, - 0x09, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x47, 0x52, 0x50, 0x43, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x47, 0x52, 0x50, 0x43, 0x12, 0x61, 0x0a, 0x1e, 0x44, - 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, - 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x18, 0x13, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x1e, - 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, - 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x24, - 0x0a, 0x0d, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4d, 0x61, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x18, - 0x19, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4d, 0x61, 0x78, - 0x53, 0x69, 0x7a, 0x65, 0x1a, 0x4f, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x88, 0x01, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x10, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, - 0x63, 0x6b, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, - 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0xa2, 0x02, 0x03, 0x53, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0xca, 0x02, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0xe2, 0x02, 0x13, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x73, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x15, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x73, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x1d, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x42, 0x65, + 0x66, 0x6f, 0x72, 0x65, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x36, 0x0a, 0x16, 0x46, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x43, 0x72, 0x69, + 0x74, 0x69, 0x63, 0x61, 0x6c, 0x18, 0x16, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x46, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x73, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x43, 0x72, 0x69, 0x74, 0x69, + 0x63, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x48, 0x54, 0x54, 0x50, + 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x48, 0x54, 0x54, + 0x50, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x47, 0x52, 0x50, 0x43, 0x18, 0x18, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x47, 0x52, 0x50, 0x43, 0x12, + 0x61, 0x0a, 0x1e, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x43, 0x72, 0x69, + 0x74, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, + 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x1e, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x43, 0x72, + 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x66, 0x74, + 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0d, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4d, 0x61, 0x78, 0x53, + 0x69, 0x7a, 0x65, 0x18, 0x19, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x4d, 0x61, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x1a, 0x4f, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x88, 0x01, 0x0a, 0x0b, 0x63, 0x6f, + 0x6d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x10, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2b, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2f, 0x70, 0x62, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0xa2, 0x02, 0x03, 0x53, 0x58, 0x58, + 0xaa, 0x02, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0xca, 0x02, 0x07, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0xe2, 0x02, 0x13, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5c, 0x47, + 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/pbservice/healthcheck.proto b/proto/pbservice/healthcheck.proto index 4ce8f8ec7..a284d7cdb 100644 --- a/proto/pbservice/healthcheck.proto +++ b/proto/pbservice/healthcheck.proto @@ -63,6 +63,7 @@ message HealthCheckDefinition { string Body = 18; bool DisableRedirects = 22; string TCP = 5; + string UDP = 23; // mog: func-to=structs.DurationFromProto func-from=structs.DurationToProto google.protobuf.Duration Interval = 6; @@ -112,6 +113,7 @@ message CheckType { string Body = 26; bool DisableRedirects = 31; string TCP = 8; + string UDP = 32; // mog: func-to=structs.DurationFromProto func-from=structs.DurationToProto google.protobuf.Duration Interval = 9; @@ -125,6 +127,7 @@ message CheckType { bool GRPCUseTLS = 15; string TLSServerName = 27; bool TLSSkipVerify = 16; + // mog: func-to=structs.DurationFromProto func-from=structs.DurationToProto google.protobuf.Duration Timeout = 17; // mog: func-to=structs.DurationFromProto func-from=structs.DurationToProto diff --git a/website/content/api-docs/agent/check.mdx b/website/content/api-docs/agent/check.mdx index 7d5ddf3cb..eafbb17c4 100644 --- a/website/content/api-docs/agent/check.mdx +++ b/website/content/api-docs/agent/check.mdx @@ -98,7 +98,7 @@ the following selectors and filter operations being supported: ## Register Check This endpoint adds a new check to the local agent. Checks may be of script, -HTTP, TCP, or TTL type. The agent is responsible for managing the status of the +HTTP, TCP, UDP, or TTL type. The agent is responsible for managing the status of the check and keeping the Catalog in sync. | Method | Path | Produces | @@ -133,7 +133,7 @@ The table below shows this endpoint's support for one of several [other methods to specify the namespace](#methods-to-specify-namespace). - `Interval` `(string: "")` - Specifies the frequency at which to run this - check. This is required for HTTP and TCP checks. + check. This is required for HTTP, TCP, and UDP checks. - `Notes` `(string: "")` - Specifies arbitrary information for humans. This is not used by Consul internally. @@ -212,7 +212,7 @@ The table below shows this endpoint's support for be set for `HTTP` checks. Each header can have multiple values. - `Timeout` `(duration: 10s)` - Specifies a timeout for outgoing connections in the - case of a Script, HTTP, TCP, or gRPC check. Can be specified in the form of "10s" + case of a Script, HTTP, TCP, UDP, or gRPC check. Can be specified in the form of "10s" or "5m" (i.e., 10 seconds or 5 minutes, respectively). - `OutputMaxSize` `(positive int: 4096)` - Allow to put a maximum size of text @@ -237,6 +237,11 @@ The table below shows this endpoint's support for made to both addresses, and the first successful connection attempt will result in a successful check. +- `UDP` `(string: "")` - Specifies a `UDP` IP address/hostname and port. +The check sends datagrams to the value specified at the interval specified in the `Interval` configuration. +If the datagram is sent successfully or a timeout is returned, the check is set to the `passing` state. +The check is logged as `critical` if the datagram is sent unsuccessfully. + - `TTL` `(duration: 10s)` - Specifies this is a TTL check, and the TTL endpoint must be used periodically to update the state of the check. If the check is not set to passing within the specified duration, then the check will be set to the failed state. diff --git a/website/content/docs/discovery/checks.mdx b/website/content/docs/discovery/checks.mdx index 20aba4b9a..5a2149579 100644 --- a/website/content/docs/discovery/checks.mdx +++ b/website/content/docs/discovery/checks.mdx @@ -85,6 +85,12 @@ There are several different kinds of checks: It is possible to configure a custom TCP check timeout value by specifying the `timeout` field in the check definition. +- `UDP + Interval` - These checks direct the client to periodically send UDP datagrams + to the specified IP/hostname and port. The duration specified in the `interval` field sets the amount of time + between attempts, such as `30s` to indicate 30 seconds. The check is logged as healthy if any response from the UDP server is received. Any other result sets the status to `critical`. + The default interval for, UDP checks is `10s`, but you can configure a custom UDP check timeout value by specifying the + `timeout` field in the check definition. If any timeout on read exists, the check is still considered healthy. + - `Time to Live (TTL)` ((#ttl)) - These checks retain their last known state for a given TTL. The state of the check must be updated periodically over the HTTP interface. If an external system fails to update the status within a given TTL, @@ -243,6 +249,35 @@ check = { +A UDP check: + + + +```hcl +check = { + id = "dns" + name = "DNS UDP on port 53" + udp = "localhost:53" + interval = "10s" + timeout = "1s" +} + +``` + +```json +{ + "check": { + "id": "dns", + "name": "DNS UDP on port 53", + "udp": "localhost:53", + "interval": "10s", + "timeout": "1s" + } +} +``` + + + A TTL check: @@ -429,7 +464,7 @@ used for any interaction with the catalog for the check, including For Alias checks, this token is used if a remote blocking query is necessary to watch the state of the aliased node or service. -Script, TCP, HTTP, Docker, and gRPC checks must include an `interval` field. This +Script, TCP, UDP, HTTP, Docker, and gRPC checks must include an `interval` field. This field is parsed by Go's `time` package, and has the following [formatting specification](https://golang.org/pkg/time/#ParseDuration): diff --git a/website/content/docs/ecs/configuration-reference.mdx b/website/content/docs/ecs/configuration-reference.mdx index 8e39efb93..54b4c2c0f 100644 --- a/website/content/docs/ecs/configuration-reference.mdx +++ b/website/content/docs/ecs/configuration-reference.mdx @@ -82,14 +82,15 @@ Defines the Consul checks for the service. Each check may contain these fields. | `h2pingUseTls` | `boolean` | optional | Specifies whether TLS is used for an h2ping check. | | `header` | `object` | optional | Specifies a set of headers that should be set for HTTP checks. Each header can have multiple values. | | `http` | `string` | optional | Specifies this is an HTTP check. Must be a URL against which request is performed every `interval`. | -| `interval` | `string` | optional | Specifies the frequency at which to run this check. Required for HTTP and TCP checks. | +| `interval` | `string` | optional | Specifies the frequency at which to run this check. Required for HTTP, TCP, and UDP checks. | | `method` | `string` | optional | Specifies the HTTP method to be used for an HTTP check. When no value is specified, `GET` is used. | | `name` | `string` | optional | The name of the check. | | `notes` | `string` | optional | Specifies arbitrary information for humans. This is not used by Consul internally. | | `status` | `string` | optional | Specifies the initial status the health check. Must be one of `passing`, `warning`, `critical`, `maintenance`, or`null`. | | `successBeforePassing` | `integer` | optional | Specifies the number of consecutive successful results required before check status transitions to passing. | | `tcp` | `string` | optional | Specifies this is a TCP check. Must be an IP/hostname plus port to which a TCP connection is made every `interval`. | -| `timeout` | `string` | optional | Specifies a timeout for outgoing connections in the case of a Script, HTTP, TCP, or gRPC check. Must be a duration string, such as `10s` or `5m`. | +| `udp` | `string` | optional | Specifies this is a UDP check. Must be an IP/hostname plus port to which UDP datagrams are sent every `interval`. | +| `timeout` | `string` | optional | Specifies a timeout for outgoing connections. Applies to script, HTTP, TCP, UDP, and gRPC checks. Must be a duration string, such as `10s` or `5m`. | | `tlsServerName` | `string` | optional | Specifies an optional string used to set the SNI host when connecting via TLS. | | `tlsSkipVerify` | `boolean` | optional | Specifies if the certificate for an HTTPS check should not be verified. | | `ttl` | `string` | optional | Specifies this is a TTL check. Must be a duration string, such as `10s` or `5m`. |