|
| 1 | +# Copyright 2026 Google LLC |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | +# |
| 15 | +"""Wire codec for ``grpc.health.v1.Health/Check`` against the daemon. |
| 16 | +
|
| 17 | +The daemon serves the standard gRPC health service. The generated Python stubs |
| 18 | +for it live in ``grpcio-health-checking``, a separate distribution this package |
| 19 | +does not depend on, and health.proto is far too small to justify adding one: the |
| 20 | +request we send is the empty message, and the response is a single varint. |
| 21 | +
|
| 22 | + HealthCheckRequest{service: ""} -> b"" |
| 23 | + HealthCheckResponse{status: S} -> b"\\x08" + varint(S) |
| 24 | + b"" -> UNKNOWN (proto3 omits 0) |
| 25 | + b"\\x08\\x01" -> SERVING |
| 26 | + b"\\x08\\x02" -> NOT_SERVING |
| 27 | +
|
| 28 | +So the two functions below are the whole codec. Note that we always probe the |
| 29 | +empty service name (""), which is the daemon's overall-process status. |
| 30 | +
|
| 31 | +This module is plain sync-only logic, shared verbatim by the async and generated |
| 32 | +sync clients; no CrossSync branching is needed. |
| 33 | +""" |
| 34 | + |
| 35 | +from __future__ import annotations |
| 36 | + |
| 37 | +import enum |
| 38 | + |
| 39 | +#: Full method name of the health check RPC, as registered by the daemon. |
| 40 | +HEALTH_CHECK_METHOD = "/grpc.health.v1.Health/Check" |
| 41 | + |
| 42 | +#: Tag byte for ``HealthCheckResponse.status``: field 1, wire type 0 (varint). |
| 43 | +_STATUS_TAG = 0x08 |
| 44 | + |
| 45 | + |
| 46 | +class ServingStatus(enum.IntEnum): |
| 47 | + """Mirror of ``grpc.health.v1.HealthCheckResponse.ServingStatus``.""" |
| 48 | + |
| 49 | + UNKNOWN = 0 |
| 50 | + SERVING = 1 |
| 51 | + NOT_SERVING = 2 |
| 52 | + SERVICE_UNKNOWN = 3 |
| 53 | + |
| 54 | + |
| 55 | +def serialize_health_request(_request: object = None) -> bytes: |
| 56 | + """Serialize ``HealthCheckRequest{service: ""}``. |
| 57 | +
|
| 58 | + Every field is at its proto3 default, so the encoding is empty. Takes and |
| 59 | + ignores an argument because gRPC calls it with the request object. |
| 60 | + """ |
| 61 | + return b"" |
| 62 | + |
| 63 | + |
| 64 | +def parse_health_response(payload: bytes) -> ServingStatus: |
| 65 | + """Decode a ``HealthCheckResponse`` into its status. |
| 66 | +
|
| 67 | + Anything that isn't a recognizable status field decodes to ``UNKNOWN``, |
| 68 | + which callers treat as a failed probe. That is the right default: the only |
| 69 | + field the message has is ``status`` at field 1, and proto3 serializes fields |
| 70 | + in ascending field-number order, so a payload that doesn't start with tag 1 |
| 71 | + is not a response we understand. |
| 72 | + """ |
| 73 | + if not payload or payload[0] != _STATUS_TAG: |
| 74 | + return ServingStatus.UNKNOWN |
| 75 | + value = 0 |
| 76 | + shift = 0 |
| 77 | + for byte in payload[1:]: |
| 78 | + value |= (byte & 0x7F) << shift |
| 79 | + if not byte & 0x80: |
| 80 | + break |
| 81 | + shift += 7 |
| 82 | + else: |
| 83 | + # Ran off the end of a continued varint: truncated message. |
| 84 | + return ServingStatus.UNKNOWN |
| 85 | + try: |
| 86 | + return ServingStatus(value) |
| 87 | + except ValueError: |
| 88 | + # A status this client predates. Not SERVING as far as we know. |
| 89 | + return ServingStatus.UNKNOWN |
0 commit comments