Skip to content

Commit

Permalink
cvplib/apiclient: Add grpc retry policy to getApiClient.
Browse files Browse the repository at this point in the history
getApiClient is creating a grpc client. Add retry policy to
the grpc client options.

Change-Id: Ibbb8d18df7d949bffe5354acd798a23c54c50371
  • Loading branch information
rcodescu committed Mar 4, 2025
1 parent 83a14d9 commit 9807eac
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 91 deletions.
4 changes: 2 additions & 2 deletions cloudvision/Connector/grpc_client/grpcClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class GRPCClient(object):
# https://grpc.github.io/grpc/core/group__grpc__arg__keys.html#ga212f667ecbcee3b100898ba7e88454df
"grpc.enable_retries": 1,
}
RETRY_POLICY_JSON = json.dumps(
GRPC_RETRY_POLICY_JSON = json.dumps(
{
"methodConfig": [
{
Expand Down Expand Up @@ -169,7 +169,7 @@ def __init__(
self.channel_options = [
(k, v) for k, v in dict(GRPCClient.DEFAULT_CHANNEL_OPTIONS, **channel_options).items()
]
self.channel_options.append(("grpc.service_config", GRPCClient.RETRY_POLICY_JSON))
self.channel_options.append(("grpc.service_config", GRPCClient.GRPC_RETRY_POLICY_JSON))
if (certs is None or key is None) and (token is None and tokenValue is None):
self.channel = grpc.insecure_channel(grpcAddr, options=self.channel_options)
else:
Expand Down
23 changes: 22 additions & 1 deletion cloudvision/cvlib/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,22 @@
USERNAME = "username"
DATASET_TYPE = "dataset_type"
ORGANIZATION = "organization"
GRPC_RETRY_POLICY_JSON = json.dumps(
{
"methodConfig": [
{
"name": [{"service": ""}],
"retryPolicy": {
"maxAttempts": 5,
"initialBackoff": "1s",
"maxBackoff": "16s",
"backoffMultiplier": 2.0,
"retryableStatusCodes": ["UNAVAILABLE"],
},
}
]
}
)

systemLogger = getLogger(__name__)

Expand Down Expand Up @@ -253,7 +269,12 @@ def add_user_context(chann):
creds = grpc.ssl_channel_credentials(root_certificates=caData)
tokCreds = grpc.access_token_call_credentials(token)
creds = grpc.composite_channel_credentials(creds, tokCreds)
self.__serviceChann = grpc.secure_channel(self.connections.serviceAddr, creds)
channel_options = []
channel_options.append(("grpc.enable_retries", 1))
channel_options.append(("grpc.service_config", GRPC_RETRY_POLICY_JSON))
self.__serviceChann = grpc.secure_channel(
self.connections.serviceAddr, creds, channel_options
)
self.__serviceChann = add_user_context(self.__serviceChann)
return stub(self.__serviceChann)

Expand Down
10 changes: 5 additions & 5 deletions test/connector/grpc_client/test_grpc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class TestGRPCClient:
("grpc.keepalive_time_ms", 60000),
("grpc.http2.max_pings_without_data", 0),
("grpc.enable_retries", 1),
("grpc.service_config", GRPCClient.RETRY_POLICY_JSON),
("grpc.service_config", GRPCClient.GRPC_RETRY_POLICY_JSON),
]
],
)
Expand All @@ -43,7 +43,7 @@ def test_channel_options_defaults(self, channel_options):
("grpc.keepalive_time_ms", 30000),
("grpc.http2.max_pings_without_data", 0),
("grpc.enable_retries", 1),
("grpc.service_config", GRPCClient.RETRY_POLICY_JSON),
("grpc.service_config", GRPCClient.GRPC_RETRY_POLICY_JSON),
],
),
(
Expand All @@ -57,7 +57,7 @@ def test_channel_options_defaults(self, channel_options):
("grpc.keepalive_time_ms", 1200000),
("grpc.http2.max_pings_without_data", 0),
("grpc.enable_retries", 1),
("grpc.service_config", GRPCClient.RETRY_POLICY_JSON),
("grpc.service_config", GRPCClient.GRPC_RETRY_POLICY_JSON),
],
),
(
Expand All @@ -73,7 +73,7 @@ def test_channel_options_defaults(self, channel_options):
("grpc.keepalive_timeout_ms", 10000),
("grpc.http2.max_pings_without_data", 1),
("grpc.enable_retries", 1),
("grpc.service_config", GRPCClient.RETRY_POLICY_JSON),
("grpc.service_config", GRPCClient.GRPC_RETRY_POLICY_JSON),
],
),
(
Expand All @@ -90,7 +90,7 @@ def test_channel_options_defaults(self, channel_options):
("grpc.keepalive_timeout_ms", 10000),
("grpc.http2.max_pings_without_data", 1),
("grpc.enable_retries", 0),
("grpc.service_config", GRPCClient.RETRY_POLICY_JSON),
("grpc.service_config", GRPCClient.GRPC_RETRY_POLICY_JSON),
],
),
],
Expand Down
82 changes: 82 additions & 0 deletions test/cvlib/context/test_get_api_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright (c) 2025 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the COPYING file.

import pytest
import time
from pathlib import Path

from concurrent import futures
from cloudvision.cvlib import Context, User, AuthAndEndpoints
from arista.tag.v2.services import (
TagServiceServicer,
TagServiceStub,
TagStreamRequest,
TagStreamResponse,
add_TagServiceServicer_to_server,
)

import grpc


THIS_DIR = Path(__file__).parent
TEST_DIR = THIS_DIR.parent.parent
TEST_DATA_DIR = Path.joinpath(TEST_DIR, "test_data")
GRPC_ATTEMPT = 1


class TagServiceMock(TagServiceServicer):
def GetAll(self, request, context):
global GRPC_ATTEMPT
if GRPC_ATTEMPT < 5:
GRPC_ATTEMPT = GRPC_ATTEMPT + 1
context.abort(grpc.StatusCode.UNAVAILABLE, "Service UNAVAILABLE")
for i in range(5):
yield TagStreamResponse()


@pytest.fixture(scope="module")
def start_server():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=1))
add_TagServiceServicer_to_server(TagServiceMock(), server)
cert_bytes = bytes()
with open(Path.joinpath(TEST_DATA_DIR, "cert.pem"), "rb") as f:
cert_bytes = f.read()
key_bytes = bytes()
with open(Path.joinpath(TEST_DATA_DIR, "key.pem"), "rb") as f:
key_bytes = f.read()
server_cred = grpc.ssl_server_credentials([(key_bytes, cert_bytes)])
server.add_secure_port("localhost:50051", server_cred)
server.start()
yield "localhost:50051"
server.stop(None)


@pytest.fixture(scope="module")
def grpc_client(start_server):
conns = AuthAndEndpoints(
serviceAddr="localhost:50051",
serviceCACert=Path.joinpath(TEST_DATA_DIR, "cert.pem"),
)
ctx = Context(user=User("test_user", "123"), connections=conns)
yield ctx


def test_get_api_client(grpc_client):
tagService = grpc_client.getApiClient(TagServiceStub)
global GRPC_ATTEMPT
try:
start_time = time.time()
responses = list(tagService.GetAll(TagStreamRequest()))
duration = time.time() - start_time
# Backoff jitter is +- 0.2. Taking this into account when checking the total duration
assert duration >= 15 * 0.8
assert duration <= 15 * 1.2
assert GRPC_ATTEMPT == 5, (
"The number of `GetAll` attempts should be increase to 5 after the Unavailables"
)
assert len(responses) == 5, (
"After the Unavailables the service should return 5 responses"
)
except grpc.RpcError:
pytest.fail("Grpc client should use the retry policy. No failure expected")
60 changes: 27 additions & 33 deletions test/test_data/cert.pem
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIF8TCCA9mgAwIBAgIQAhuayHQfmDAZvTfM0RS4uTANBgkqhkiG9w0BAQsFADCB
mDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh
biBGcmFuY2lzY28xGDAWBgNVBAoTD0FyaXN0YSBOZXR3b3JrczEnMCUGA1UECxMe
QXV0b2dlbmVyYXRlZCBhZXJpcyBjbHVzdGVyIENBMRkwFwYDVQQDExBBdXRvZ2Vu
ZXJhdGVkIENBMB4XDTIzMDExNjA3MjcyOVoXDTIzMDQxNjA3MjcyOVowdzELMAkG
A1UEBhMCSU4xCzAJBgNVBAgTAk1IMQ0wCwYDVQQHEwRQdW5lMRgwFgYDVQQKEw9B
cmlzdGEgTmV0d29ya3MxFDASBgNVBAsTC0RldmVsb3BtZW50MQ0wCwYDVQQDEwR0
ZXN0MQ0wCwYDVQQFEwQ2MTc5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
AgEAwKlu1h8AJYr03vsDzZnfcvISDIwt5s2qES0O2vpm6X31wCcwmKS24J5zADcw
JIrdJ7/KtsUvSjZN0qG9Oc1a7zXP/1aDFLPZ8ZWUl1DTE+qKBFB1WpIAHgLK90s8
fpRI63D6dK9JV1+gw/TL5fFDy7nM9VpXJsCbYTmdpLVm/6QAJqTTl8ViL2IzsqMD
1968CY0iS0gOUakJ/nO0KsjVbPQ5vBA0HLdEeyZzshBRlI24C4OQsadEi6w/DMsW
Wlygh064ykasnSPsWKxwZGSqgQ+vTouOlbkCpsblmr9LWA0cMXSvo8Z2sJdsy4Kd
7NqHzIshTCMkwNilkWUaxlCvQ5FEN6Z6Jdawle3MAB3li5oSKxtK3ZwI27FDaKak
abyffYkB+pSvssrsyLxRKQ1nQx3lV1bqWIM++IgDjBRvHuenYTm9Oug7YIVN8nI8
Kxz4OAs3E0he1H7x+aoCbauDCr8KDzHoVfA3aFb5SiD1a0r8tWfWU97TY/EovAvS
23bxL8gtZQ7GljvDrVMCAOl1BnHcVeOa+VgzNZcb1ZoHsmLNZm//p3EL1DcphJph
84trkj4VKsPSj3kjAYRISGN2pTIoSgejw3Lot4dT0gkI2i5IX9t7BisoEUnuRVrL
V8D2s5hcVYj/O2J6CaSv/yJVK65WLlqszSwtX2/B22OaI1sCAwEAAaNXMFUwDgYD
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
-----END CERTIFICATE-----

100 changes: 50 additions & 50 deletions test/test_data/key.pem
Original file line number Diff line number Diff line change
@@ -1,52 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDAqW7WHwAlivTe
+wPNmd9y8hIMjC3mzaoRLQ7a+mbpffXAJzCYpLbgnnMANzAkit0nv8q2xS9KNk3S
ob05zVrvNc//VoMUs9nxlZSXUNMT6ooEUHVakgAeAsr3Szx+lEjrcPp0r0lXX6DD
9Mvl8UPLucz1WlcmwJthOZ2ktWb/pAAmpNOXxWIvYjOyowPX3rwJjSJLSA5RqQn+
c7QqyNVs9Dm8EDQct0R7JnOyEFGUjbgLg5Cxp0SLrD8MyxZaXKCHTrjKRqydI+xY
rHBkZKqBD69Oi46VuQKmxuWav0tYDRwxdK+jxnawl2zLgp3s2ofMiyFMIyTA2KWR
ZRrGUK9DkUQ3pnol1rCV7cwAHeWLmhIrG0rdnAjbsUNopqRpvJ99iQH6lK+yyuzI
vFEpDWdDHeVXVupYgz74iAOMFG8e56dhOb066DtghU3ycjwrHPg4CzcTSF7UfvH5
qgJtq4MKvwoPMehV8DdoVvlKIPVrSvy1Z9ZT3tNj8Si8C9LbdvEvyC1lDsaWO8Ot
UwIA6XUGcdxV45r5WDM1lxvVmgeyYs1mb/+ncQvUNymEmmHzi2uSPhUqw9KPeSMB
hEhIY3alMihKB6PDcui3h1PSCQjaLkhf23sGKygRSe5FWstXwPazmFxViP87YnoJ
pK//IlUrrlYuWqzNLC1fb8HbY5ojWwIDAQABAoICAQCBbBBpxI97qNoQRH1si1Zg
yB650R0ws63wNd2J2rZxWc1RlMH/eH7la2uDlI9BrQsdnahWmSXIa6xht4b/ImVY
1kRwmbQanyrkMACOmZwCAAdRGJ+NBRERF/0ynJb5zTEjAFUvN2VETZgYqwSh5i4N
2DXICDUNckuux16P+9BCIPB9BTKi/NSVUhm6gemeBSE6JVYJTlFYJ/7vsNgx+ZHt
zC5QmG35cF+PfugvkOjMq1scWPhFb0ELttzwAil08E5iNs7KCyn/sdceRlCGEF29
r0SrtfJFv+/5CxnIl4LMlpn+2wz2Lucif8Z0+7yNFMwu6rOmiuEwrxiiEFIpMKBa
z9SYowTzDMcjp1OClQg/6zyvwcYNkkwhKfGMSDPCUhhe8wojLeAgOSzvnU+g/Bxc
6bFIcDH7e8C/fqG2OFbWFE+gKQJgxldCuhkw7smnWAUDV/wJzjCLY9mx0fGveJVe
RETpMo7HBBLcxyO/vRxeiFOevZUOZM2tdBKAUV124VkPqubFGKmhv4MNx7meqmZ6
LDylYX2eOzVv1AntUbA91vHM0sSqboL4qjK0EyISHms1eNQAEib1eWH340PQQJN7
SZCQJXypIBxYEK1VEQCVd4kEs0SalcSh5Z5iEN5oGj6fs4p08w+nZ7UeTHfr4OR5
BK4AmLHshFCmeH0WzTiNMQKCAQEA5iY1OK+RS86DnAib7L4iV3GLg3p3xXewvIGN
WYQEavH+ohSE4tgyCqq6JxYc+t07rXaPKSVq4FeVkrTAfPs9FJsWw40bBrt8gx04
h/CoII9z5XDFq78mV4Zg/OkbKUZwkn782b+MPyySQP3Msv8juZB8VeJr0Yj32Uhs
QgtxbuM5v//VcVTuiRKLNO5L4Efs8ZV5HGo8ryd3yaq5YEWXv9bFShjlwZmilz1K
xxDGM51pHJi2wJlwdN0l4TBzU5dTo52Bp3bMqHXJz1MeuQnOjyhWoLMoJFqjRZHj
J6FaqFoO6stNsQAjh91YXI/YZB00j/mz/RUv8g8aF21tGBecmQKCAQEA1k1MlpdG
ccj0YDlRUFKauAJEUe0Xm8rBc1TVCkRPrUr8UKFCo9Z6m9apx9SO5yKJJUHBr8Ug
ujbf3wkd0E3tDVUONcSEAkWjBnP/lVkKyGAp9FcSU+sVkb0xUxrcX0LcI9xQfbZC
sDwZzNQB9Q16KMWBEZrkdwctcH1JsG1nfc/dJ/ciPY7myeH/81dTfD73JINlw6G1
w9BNMFY+UgswLH5AUL4ZCRAIKGbQRNiOwb0ln8zMRRmGeRdZV31i2AoX90rNpMfb
cF+uKXpO+BDTFrGTRG/+Y9hVqDR+0DuVqz9MH0x+Xwaq0SvmuostjyUnmidwWqiU
rATj4wpb9vQkEwKCAQAsfmp/oQGYBD7RZaCvbjq5aQwtyPlqxa73YpxvF+S6wDoY
H5tpN57FDSib8dx8a1TkPi2DKOa2pHgYOrWT9AZk+261M4xsCscRmg6ZyV7XnKQt
UVYF1BiXhzUs7+v+WM3epIpBlpSIihdkVSzD1iuT7mj7OaDai35PFX9IbWnqpCdA
BTfAh0juWxVqkD8/Bui588nMQoWk3x57cHQSNqIVxnGx14pjmU4jqwfP4GBRDjeJ
/cQnDAL/tNlj5bgGCxVyZjrwozkBwHPZjxo7b7ftWUEWJEy4aoVyJ7ggq0MZlP+Z
SOCmPX6g/5vbmTuebz8ALqgrX3M7esvyh4c1xyrRAoIBAG7cWCWDJmcQjRMGM8F4
qh9M1OkI3PC0J/UwynDgO9Oq+fIAKkg1WQIrP3Nny3FYlVgXt0nlkXOjlZZ80daD
qYD/IjXAboX6XkalVW9+O6zCypFjRdDxyRu2osCaIayKGvXWMDGISuF+hd5HQqfx
i+NxoL5pwYytwtzxg2BoiGQ2VvMozqbHuS5w4oaRA1g//nv+GXPlwPEGXhgGVYD9
1rL9sOnUHYFpsL6tyUoEOX2SCterC3UnqkAUSxyOecbHGfTM1qtK1INCjAdbBWUg
1wJjZIq/4Prn3BAC8CNq35dSTF/Yo3snKNDChD62ZyApGV6OznmnwhlnAjjekAql
688CggEAITOqI+Qp1WaKAWahmSkQgbohXu5JlR8o0DyN/CsTUICAQIuXVQN19D8g
8B4r0AIcYIW+DwPxbLm6YWwpW7ULMFgRcwwkCcKkpO0BpknzM7z+oRylT35r146a
A2OP0cp7xlXQS2ZkdmODmi7d39H9olKIYfze8a+8GkzQkyZrs9cpB7jo1c0i/24P
ZatxT62ioalyzQ/xfzZGRwVlS8BPcs/zTMWRb2jkDolk91Va+D0u12FT0GAGyj4+
9SDv50wUdXpYa1gxpjmG8kmgsetUtsnvzDPcAUZN+XjbwpkNlGz+YgoBZLLUwB/1
rw3KyKBloaHCCig8KSLNC1s155kHLA==
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
7kfdWADrK3Pb3gl24MrrvAOXdIiz2A==
-----END PRIVATE KEY-----

0 comments on commit 9807eac

Please sign in to comment.