Skip to content

Commit 817dbda

Browse files
Timestamped gNMI message logs (#396)
* Remove not-needed debug log * Relative Timestamp gnmi Logs These are timestamped to help on troubleshooting delays and retries.
1 parent c9e3a43 commit 817dbda

File tree

3 files changed

+70
-44
lines changed

3 files changed

+70
-44
lines changed

oc_config_validate/README.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ looking for:
116116
[--target_cert_as_root_ca | --root_ca_cert FILE ] \
117117
[--cert_chain FILE --private_key FILE]
118118
[-init INIT_CONFIG_FILE -xpath INIT_CONFIG_XPATH] \
119-
[--verbose] [--log_gnmi] [--stop_on_error]
119+
[--gnmi_set_cooldown_secs GNMI_SET_COOLDOWN_SECS] \
120+
[--log_gnmi] [--stop_on_error] \
121+
[--verbose]
120122
```
121123
122124
For an example:
@@ -163,13 +165,34 @@ Run `python3 -m oc_config_validate -models` to get a list of the versions (revis
163165
#### Timing in gNMI Sets and Gets
164166
165167
The Target can take some time to process configurations done via gNMI Set messages. By default, `oc_config_validate` waits 10 seconds after a successful gNMI Set message.
166-
This time is customized with the `gnmi_set_cooldown_secs` option in the targe configuration or in a command line argument.
168+
This time is customized with the `gnmi_set_cooldown_secs` option in the target configuration or in a command line argument.
167169
168170
Similarly, the Target can take some time to show the expected values in a gNMI Get message.
169171
Some testcases have a retry mechanism, that will repeat the gNMI Get message and comparisons (against an OC model, against an expected JSON text, or both) until it succeeds or times out.
170172
171173
See [here](https://github.com/google/gnxi/blob/master/oc_config_validate/docs/testclasses.md) for more details.
172174
175+
#### Timestamped Logging gNMI messages
176+
177+
With `log_gnmi` option in the command line argument, all gNMI Messages sent and received are logged with a relative timestamp to the beginning of the test.
178+
179+
The gNMI messages logs are in the format `[<relative_time_secs>]gNMI <Operation>(<xpath(s)>) =>|=< <[values]>`. E.g.:
180+
181+
```
182+
[0.00s]gNMI Subscribe Sample(interfaces/interface[name=*]/state/oper-status) =>
183+
[5.00s]gNMI Subscribe Sample(interfaces/interface[name=*]/state/oper-status) <= [
184+
{
185+
"timestamp": 1754947845878668496, ...
186+
187+
INFO(testbase.py:120):[0.00s]gNMI Set Update(system/clock/config) => {'openconfig-system:timezone-name': 'Europe/Paris'}
188+
INFO(testbase.py:120):[2.00s]gNMI Get(system/clock/state) =>
189+
INFO(testbase.py:120):[2.00s]gNMI Get(system/clock/state) <= json_ietf_val: "{\"openconfig-system:timezone-name\":\"Europe/Stockholm\"}"
190+
```
191+
192+
The messages are logged to the TestCase results and also to STDOUT, as INFO.
193+
194+
> Because the TestCase results logs are printed to STDOUT upon test failure, the gNMI messages will be seen twice in SDTOUT for failed test.
195+
173196
## Copyright
174197
175198
Copyright 2022

oc_config_validate/oc_config_validate/target.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ def gNMIConnect(self):
113113
self.channel = grpc.insecure_channel(self.address)
114114
else:
115115
creds = self._buildCredentials()
116-
logging.debug("creds: %s", creds)
117116
if self.host_tls_override:
118117
self.channel = grpc.secure_channel(
119118
self.address, creds,

oc_config_validate/oc_config_validate/testbase.py

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
# Switch to enable logging of gNMI messages in the testcase logs.
3333
LOG_GNMI = False
3434

35+
LOG_TIMESTAMP_PREFIX = "[%.2fs]"
36+
3537

3638
def failfast(method):
3739
"""Wrap a test to stop upon failure."""
@@ -100,6 +102,25 @@ class TestCase(unittest.case.TestCase):
100102
duration_sec = 0
101103
start_time_sec = 0
102104

105+
def log_gnmi(self, formatter, *args):
106+
"""Optionally logs a timestamped message during a test.
107+
108+
The timestamp is the time since the test started, in seconds.
109+
110+
It logs also to STDOUT as INFO.
111+
112+
Args:
113+
formatter: The base python string formatter.
114+
*args: The formatter parameters. Allows logging messages during
115+
test executions.
116+
"""
117+
if LOG_GNMI:
118+
timestamp = int(time.time() - self.start_time_sec)
119+
if self.result:
120+
self.result.log(LOG_TIMESTAMP_PREFIX +
121+
formatter, timestamp, *args)
122+
logging.info(LOG_TIMESTAMP_PREFIX + formatter, timestamp, *args)
123+
103124
def phony(self):
104125
"""A phony test method to be used for testing."""
105126

@@ -137,20 +158,18 @@ def gNMIGet(self, xpath: str) -> Optional[gnmi_pb2.TypedValue]:
137158
Returns:
138159
A single response value, or None if error.
139160
"""
161+
self.log_gnmi("gNMI Get(%s) => ", xpath)
140162
try:
141163
resp = self.test_target.gNMIGet(xpath)
142164
except Exception as err:
143-
self.log("Get(%s) <= Error: %s", xpath, err)
165+
self.log("gNMI Get(%s) <= Error: %s", xpath, err)
144166
return None
145167
try:
146168
resp_val = resp.notification[0].update[0].val
147169
except IndexError:
148-
self.log("Get(%s) <= Bad response: %s", xpath, resp)
170+
self.log("gNMI Get(%s) <= Bad response: %s", xpath, resp)
149171
return None
150-
if LOG_GNMI:
151-
msg = ("gNMI Get(%s) <= %s", xpath, resp_val)
152-
self.log(*msg)
153-
logging.info(*msg)
172+
self.log_gnmi("gNMI Get(%s) <= %s", xpath, resp_val)
154173
return resp_val
155174

156175
def gNMIGetAssertJson(self, xpath: str) -> Union[str, bytes]:
@@ -215,14 +234,11 @@ def gNMISetUpdate(self, xpath: str, value: Any) -> bool:
215234
Returns:
216235
False if the gNMI Set did not succeed.
217236
"""
218-
if LOG_GNMI:
219-
msg = ("gNMI Set Update(%s) => %s", xpath, value)
220-
self.log(*msg)
221-
logging.info(*msg)
237+
self.log_gnmi("gNMI Set Update(%s) => %s", xpath, value)
222238
try:
223239
self.test_target.gNMISetUpdate(xpath, value)
224240
except Exception as err:
225-
self.log("Set(%s) <= Error: %s", xpath, err)
241+
self.log("gNMI Set Update(%s) <= Error: %s", xpath, err)
226242
return False
227243
return True
228244

@@ -238,14 +254,11 @@ def gNMISetDelete(self, xpath: str) -> bool:
238254
Returns:
239255
A single response value, or None if error.
240256
"""
241-
if LOG_GNMI:
242-
msg = ("gNMI Set Delete(%s) =>", xpath)
243-
self.log(*msg)
244-
logging.info(*msg)
257+
self.log_gnmi("gNMI Set Delete(%s) =>", xpath)
245258
try:
246259
self.test_target.gNMISetDelete(xpath)
247260
except Exception as err:
248-
self.log("Set(%s) <= Error: %s", xpath, err)
261+
self.log("gNMI Set Delete(%s) <= Error: %s", xpath, err)
249262
return False
250263
return True
251264

@@ -259,14 +272,11 @@ def gNMISetReplace(self, xpath: str, value: Any) -> bool:
259272
Returns:
260273
False if the gNMI Set did not succeed.
261274
"""
262-
if LOG_GNMI:
263-
msg = ("gNMI Set Replace(%s) => %s", xpath, value)
264-
self.log(*msg)
265-
logging.info(*msg)
275+
self.log("gNMI Set Replace(%s) => %s", xpath, value)
266276
try:
267277
self.test_target.gNMISetReplace(xpath, value)
268278
except Exception as err:
269-
self.log("Set(%s) <= Error: %s", xpath, err)
279+
self.log("gNMI Set Replace(%s) <= Error: %s", xpath, err)
270280
return False
271281
return True
272282

@@ -282,16 +292,15 @@ def gNMISubsOnce(self, xpaths: List[str]) -> Optional[
282292
Returns:
283293
A list of Notifications received, or None if error.
284294
"""
295+
self.log_gnmi("gNMI Subscribe Once(%s) => ", xpaths)
285296
try:
286297
resp = self.test_target.gNMISubsOnce(xpaths)
287298
except Exception as err:
288-
self.log("SubscribeOnce(%s) <= Error: %s", xpaths, err)
299+
self.log("gNMI Subscribe Once(%s) <= Error: %s", xpaths, err)
289300
return None
290-
if LOG_GNMI:
291-
msg = ("gNMI SubscribeOnce(%s) <= %s", xpaths,
292-
schema.notificationsJsonString(resp))
293-
self.log(*msg)
294-
logging.info(*msg)
301+
302+
self.log_gnmi("gNMI Subscribe Once(%s) <= %s", xpaths,
303+
schema.notificationsJsonString(resp))
295304
return resp
296305

297306
def gNMISubsStreamSample(
@@ -311,17 +320,15 @@ def gNMISubsStreamSample(
311320
Returns:
312321
A list of Notifications received, or None if error.
313322
"""
323+
self.log_gnmi("gNMI Subscribe Sample(%s) =>", xpath)
314324
try:
315325
resp = self.test_target.gNMISubsStreamSample(
316326
xpath, sample_interval, timeout)
317327
except Exception as err:
318-
self.log("SubscribeStream(%s) <= Error: %s", xpath, err)
328+
self.log("gNMI Subscribe Sample(%s) <= Error: %s", xpath, err)
319329
return None
320-
if LOG_GNMI:
321-
msg = ("gNMI SubscribeSample(%s) <= %s", xpath,
322-
schema.notificationsJsonString(resp))
323-
self.log(*msg)
324-
logging.info(*msg)
330+
self.log_gnmi("gNMI Subscribe Sample(%s) <= %s", xpath,
331+
schema.notificationsJsonString(resp))
325332
return resp
326333

327334
def gNMISubsStreamOnChange(
@@ -353,18 +360,15 @@ def gNMISubsStreamOnChange(
353360
2 Lists of gnmi_pb2.Notification objects received,
354361
before and after sync_response.
355362
"""
356-
363+
self.log_gnmi("gNMI Subscribe OnChange(%s) =>\n", xpath)
357364
try:
358365
before, after = self.test_target.gNMISubsStreamOnChange(
359366
xpath, timeout, sync_response_callback)
360367
except Exception as err:
361-
self.log("SubscribeOnChange(%s) <= Error: %s", xpath, err)
368+
self.log("gNMI Subscribe OnChange(%s) <= Error: %s", xpath, err)
362369
return None, None
363-
if LOG_GNMI:
364-
msg = ("gNMI SubscribeOnChange(%s) <= %s\n", xpath,
365-
schema.notificationsJsonString(before + after))
366-
self.log(*msg)
367-
logging.info(*msg)
370+
self.log_gnmi("gNMI Subscribe OnChange(%s) <= %s\n", xpath,
371+
schema.notificationsJsonString(before + after))
368372
return before, after
369373

370374
@classmethod
@@ -492,7 +496,7 @@ def log(self, formatter, *args):
492496
*args: The formatter parameters. Allows logging messages during
493497
test executions.
494498
"""
495-
self.log_message += formatter % args + '\n'
499+
self.log_message += (formatter % args) + '\n'
496500

497501
def startTest(self, test: TestCase): # pytype: disable=signature-mismatch
498502
"""Clean the log before every test."""

0 commit comments

Comments
 (0)