Add output.xml with only INFO logging leve
[csit.git] / resources / libraries / python / Classify.py
1 # Copyright (c) 2018 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Classify utilities library."""
15
16 from robot.api import logger
17
18 from resources.libraries.python.VatExecutor import VatExecutor, VatTerminal
19 from resources.libraries.python.topology import Topology
20
21
22 class Classify(object):
23     """Classify utilities."""
24
25     @staticmethod
26     def vpp_creates_classify_table_l3(node, ip_version, direction):
27         """Create classify table for IP address filtering.
28
29         :param node: VPP node to create classify table.
30         :param ip_version: Version of IP protocol.
31         :param direction: Direction of traffic - src/dst.
32         :type node: dict
33         :type ip_version: str
34         :type direction: str
35         :returns: (table_index, skip_n, match_n)
36             table_index: Classify table index.
37             skip_n: Number of skip vectors.
38             match_n: Number of match vectors.
39         :rtype: tuple(int, int, int)
40         :raises RuntimeError: If VPP can't create table.
41         """
42
43         output = VatExecutor.cmd_from_template(node, "classify_add_table.vat",
44                                                ip_version=ip_version,
45                                                direction=direction)
46
47         if output[0]["retval"] == 0:
48             table_index = output[0]["new_table_index"]
49             skip_n = output[0]["skip_n_vectors"]
50             match_n = output[0]["match_n_vectors"]
51             logger.trace('Classify table with table_index {} created on node {}'
52                          .format(table_index, node['host']))
53         else:
54             raise RuntimeError('Unable to create classify table on node {}'
55                                .format(node['host']))
56
57         return table_index, skip_n, match_n
58
59     @staticmethod
60     def vpp_creates_classify_table_l2(node, direction):
61         """Create classify table for MAC address filtering.
62
63         :param node: VPP node to create classify table.
64         :param direction: Direction of traffic - src/dst.
65         :type node: dict
66         :type direction: str
67         :returns: (table_index, skip_n, match_n)
68             table_index: Classify table index.
69             skip_n: Number of skip vectors.
70             match_n: Number of match vectors.
71         :rtype: tuple(int, int, int)
72         :raises RuntimeError: If VPP can't create table.
73         """
74         output = VatExecutor.cmd_from_template(node,
75                                                "classify_add_table_l2.vat",
76                                                direction=direction)
77
78         if output[0]["retval"] == 0:
79             table_index = output[0]["new_table_index"]
80             skip_n = output[0]["skip_n_vectors"]
81             match_n = output[0]["match_n_vectors"]
82             logger.trace('Classify table with table_index {} created on node {}'
83                          .format(table_index, node['host']))
84         else:
85             raise RuntimeError('Unable to create classify table on node {}'
86                                .format(node['host']))
87
88         return table_index, skip_n, match_n
89
90     @staticmethod
91     def vpp_creates_classify_table_hex(node, hex_mask):
92         """Create classify table with hex mask.
93
94         :param node: VPP node to create classify table based on hex mask.
95         :param hex_mask: Classify hex mask.
96         :type node: dict
97         :type hex_mask: str
98         :returns: (table_index, skip_n, match_n)
99             table_index: Classify table index.
100             skip_n: Number of skip vectors.
101             match_n: Number of match vectors.
102         :rtype: tuple(int, int, int)
103         :raises RuntimeError: If VPP can't create table.
104         """
105         output = VatExecutor.cmd_from_template(node,
106                                                "classify_add_table_hex.vat",
107                                                hex_mask=hex_mask)
108
109         if output[0]["retval"] == 0:
110             table_index = output[0]["new_table_index"]
111             skip_n = output[0]["skip_n_vectors"]
112             match_n = output[0]["match_n_vectors"]
113             logger.trace('Classify table with table_index {} created on node {}'
114                          .format(table_index, node['host']))
115         else:
116             raise RuntimeError('Unable to create classify table on node {}'
117                                .format(node['host']))
118
119         return table_index, skip_n, match_n
120
121     @staticmethod
122     def vpp_configures_classify_session_l3(node, acl_method, table_index,
123                                            skip_n, match_n, ip_version,
124                                            direction, address):
125         """Configuration of classify session for IP address filtering.
126
127         :param node: VPP node to setup classify session.
128         :param acl_method: ACL method - deny/permit.
129         :param table_index: Classify table index.
130         :param skip_n: Number of skip vectors based on mask.
131         :param match_n: Number of match vectors based on mask.
132         :param ip_version: Version of IP protocol.
133         :param direction: Direction of traffic - src/dst.
134         :param address: IPv4 or IPv6 address.
135         :type node: dict
136         :type acl_method: str
137         :type table_index: int
138         :type skip_n: int
139         :type match_n: int
140         :type ip_version: str
141         :type direction: str
142         :type address: str
143         """
144         with VatTerminal(node) as vat:
145             vat.vat_terminal_exec_cmd_from_template("classify_add_session.vat",
146                                                     acl_method=acl_method,
147                                                     table_index=table_index,
148                                                     skip_n=skip_n,
149                                                     match_n=match_n,
150                                                     ip_version=ip_version,
151                                                     direction=direction,
152                                                     address=address)
153
154     @staticmethod
155     def vpp_configures_classify_session_l2(node, acl_method, table_index,
156                                            skip_n, match_n, direction, address):
157         """Configuration of classify session for MAC address filtering.
158
159         :param node: VPP node to setup classify session.
160         :param acl_method: ACL method - deny/permit.
161         :param table_index: Classify table index.
162         :param skip_n: Number of skip vectors based on mask.
163         :param match_n: Number of match vectors based on mask.
164         :param direction: Direction of traffic - src/dst.
165         :param address: IPv4 or IPv6 address.
166         :type node: dict
167         :type acl_method: str
168         :type table_index: int
169         :type skip_n: int
170         :type match_n: int
171         :type direction: str
172         :type address: str
173         """
174         with VatTerminal(node) as vat:
175             vat.vat_terminal_exec_cmd_from_template(
176                 "classify_add_session_l2.vat",
177                 acl_method=acl_method,
178                 table_index=table_index,
179                 skip_n=skip_n,
180                 match_n=match_n,
181                 direction=direction,
182                 address=address)
183
184     @staticmethod
185     def vpp_configures_classify_session_hex(node, acl_method, table_index,
186                                             skip_n, match_n, hex_value):
187         """Configuration of classify session with hex value.
188
189         :param node: VPP node to setup classify session.
190         :param acl_method: ACL method - deny/permit.
191         :param table_index: Classify table index.
192         :param skip_n: Number of skip vectors based on mask.
193         :param match_n: Number of match vectors based on mask.
194         :param hex_value: Classify hex value.
195         :type node: dict
196         :type acl_method: str
197         :type table_index: int
198         :type skip_n: int
199         :type match_n: int
200         :type hex_value: str
201         """
202         with VatTerminal(node) as vat:
203             vat.vat_terminal_exec_cmd_from_template(
204                 "classify_add_session_hex.vat",
205                 acl_method=acl_method,
206                 table_index=table_index,
207                 skip_n=skip_n,
208                 match_n=match_n,
209                 hex_value=hex_value)
210
211     @staticmethod
212     def vpp_configures_classify_session_generic(node, session_type, table_index,
213                                                 skip_n, match_n, match,
214                                                 match2=''):
215         """Configuration of classify session.
216
217         :param node: VPP node to setup classify session.
218         :param session_type: Session type - hit-next, l2-hit-next, acl-hit-next
219             or policer-hit-next, and their respective parameters.
220         :param table_index: Classify table index.
221         :param skip_n: Number of skip vectors based on mask.
222         :param match_n: Number of match vectors based on mask.
223         :param match: Match value - l2, l3, l4 or hex, and their
224             respective parameters.
225         :param match2: Additional match values, to avoid using overly long
226             variables in RobotFramework.
227         :type node: dict
228         :type session_type: str
229         :type table_index: int
230         :type skip_n: int
231         :type match_n: int
232         :type match: str
233         :type match2: str
234         """
235
236         match = ' '.join((match, match2))
237
238         with VatTerminal(node) as vat:
239             vat.vat_terminal_exec_cmd_from_template(
240                 "classify_add_session_generic.vat",
241                 type=session_type,
242                 table_index=table_index,
243                 skip_n=skip_n,
244                 match_n=match_n,
245                 match=match,
246             )
247
248     @staticmethod
249     def compute_classify_hex_mask(ip_version, protocol, direction):
250         """Compute classify hex mask for TCP or UDP packet matching.
251
252         :param ip_version: Version of IP protocol.
253         :param protocol: Type of protocol.
254         :param direction: Traffic direction.
255         :type ip_version: str
256         :type protocol: str
257         :type direction: str
258         :returns: Classify hex mask.
259         :rtype: str
260         :raises ValueError: If protocol is not TCP or UDP.
261         :raises ValueError: If direction is not source or destination or
262                             source + destination.
263         """
264         if protocol == 'TCP' or protocol == 'UDP':
265             base_mask = Classify._compute_base_mask(ip_version)
266
267             if direction == 'source':
268                 return base_mask + 'FFFF0000'
269             elif direction == 'destination':
270                 return base_mask + '0000FFFF'
271             elif direction == 'source + destination':
272                 return base_mask + 'FFFFFFFF'
273             else:
274                 raise ValueError("Invalid direction!")
275         else:
276             raise ValueError("Invalid protocol!")
277
278     @staticmethod
279     def compute_classify_hex_value(hex_mask, source_port, destination_port):
280         """Compute classify hex value for TCP or UDP packet matching.
281
282         :param hex_mask: Classify hex mask.
283         :param source_port: Source TCP/UDP port.
284         :param destination_port: Destination TCP/UDP port.
285         :type hex_mask: str
286         :type source_port: str
287         :type destination_port: str
288         :returns: Classify hex value.
289         :rtype: str
290         """
291         source_port_hex = Classify._port_convert(source_port)
292         destination_port_hex = Classify._port_convert(destination_port)
293
294         return hex_mask[:-8] + source_port_hex + destination_port_hex
295
296     @staticmethod
297     def _port_convert(port):
298         """Convert port number for classify hex table format.
299
300         :param port: TCP/UDP port number.
301         :type port: str
302         :returns: TCP/UDP port number in 4-digit hexadecimal format.
303         :rtype: str
304         """
305         return '{0:04x}'.format(int(port))
306
307     @staticmethod
308     def _compute_base_mask(ip_version):
309         """Compute base classify hex mask based on IP version.
310
311         :param ip_version: Version of IP protocol.
312         :type ip_version: str
313         :returns: Base hex mask.
314         :rtype: str
315         """
316         if ip_version == 'ip4':
317             return 68 * '0'
318             # base value of classify hex table for IPv4 TCP/UDP ports
319         elif ip_version == 'ip6':
320             return 108 * '0'
321             # base value of classify hex table for IPv6 TCP/UDP ports
322         else:
323             raise ValueError("Invalid IP version!")
324
325     @staticmethod
326     def get_classify_table_data(node, table_index):
327         """Retrieve settings for classify table by ID.
328
329         :param node: VPP node to retrieve classify data from.
330         :param table_index: Index of a specific classify table.
331         :type node: dict
332         :type table_index: int
333         :returns: Classify table settings.
334         :rtype: dict
335         """
336         with VatTerminal(node) as vat:
337             data = vat.vat_terminal_exec_cmd_from_template(
338                 "classify_table_info.vat",
339                 table_id=table_index
340             )
341         return data[0]
342
343     @staticmethod
344     def get_classify_session_data(node, table_index, session_index=None):
345         """Retrieve settings for all classify sessions in a table,
346         or for a specific classify session.
347
348         :param node: VPP node to retrieve classify data from.
349         :param table_index: Index of a classify table.
350         :param session_index: Index of a specific classify session. (Optional)
351         :type node: dict
352         :type table_index: int
353         :type session_index: int
354         :returns: List of classify session settings, or a dictionary of settings
355          for a specific classify session.
356         :rtype: list or dict
357         """
358         with VatTerminal(node) as vat:
359             data = vat.vat_terminal_exec_cmd_from_template(
360                 "classify_session_dump.vat",
361                 table_id=table_index
362             )
363         if session_index is not None:
364             return data[0][session_index]
365         return data[0]
366
367     @staticmethod
368     def vpp_log_plugin_acl_settings(node):
369         """Retrieve configured settings from the ACL plugin
370          and write to robot log.
371
372         :param node: VPP node.
373         :type node: dict
374         """
375         try:
376             VatExecutor.cmd_from_template(
377                 node, "acl_plugin/acl_dump.vat")
378         except (ValueError, RuntimeError):
379             # Fails to parse JSON data in response, but it is still logged
380             pass
381
382     @staticmethod
383     def vpp_log_plugin_acl_interface_assignment(node):
384         """Retrieve interface assignment from the ACL plugin
385         and write to robot log.
386
387         :param node: VPP node.
388         :type node: dict
389         """
390         try:
391             VatExecutor.cmd_from_template(
392                 node, "acl_plugin/acl_interface_dump.vat", json_out=False)
393         except RuntimeError:
394             # Fails to parse response, but it is still logged
395             pass
396
397     @staticmethod
398     def set_acl_list_for_interface(node, interface, acl_type, acl_idx=None):
399         """Set the list of input or output ACLs applied to the interface. It
400         unapplies any previously applied ACLs.
401
402         :param node: VPP node to set ACL on.
403         :param interface: Interface name or sw_if_index.
404         :param acl_type: Type of ACL(s) - input or output.
405         :param acl_idx: Index(ies) of ACLs to be applied on the interface.
406         :type node: dict
407         :type interface: str or int
408         :type acl_type: str
409         :type acl_idx: list
410         :raises RuntimeError: If unable to set ACL list for the interface.
411         """
412         if isinstance(interface, basestring):
413             sw_if_index = Topology.get_interface_sw_index(node, interface)
414         else:
415             sw_if_index = interface
416
417         acl_list = acl_type + ' ' + ' '.join(str(idx) for idx in acl_idx) \
418             if acl_idx else acl_type
419
420         try:
421             with VatTerminal(node, json_param=False) as vat:
422                 vat.vat_terminal_exec_cmd_from_template(
423                     "acl_plugin/acl_interface_set_acl_list.vat",
424                     interface=sw_if_index, acl_list=acl_list)
425         except RuntimeError:
426             raise RuntimeError("Setting of ACL list for interface {0} failed "
427                                "on node {1}".format(interface, node['host']))
428
429     @staticmethod
430     def add_replace_acl(node, acl_idx=None, ip_ver="ipv4", action="permit",
431                         src=None, dst=None, sport=None, dport=None, proto=None,
432                         tcpflg_val=None, tcpflg_mask=None):
433         """Add a new ACL or replace the existing one. To replace an existing
434         ACL, pass the ID of this ACL.
435
436         :param node: VPP node to set ACL on.
437         :param acl_idx: ID of ACL. (Optional)
438         :param ip_ver: IP version. (Optional)
439         :param action: ACL action. (Optional)
440         :param src: Source IP in format IP/plen. (Optional)
441         :param dst: Destination IP in format IP/plen. (Optional)
442         :param sport: Source port or ICMP4/6 type - range format X-Y allowed.
443          (Optional)
444         :param dport: Destination port or ICMP4/6 code - range format X-Y
445          allowed. (Optional)
446         :param proto: L4 protocol (http://www.iana.org/assignments/protocol-
447          numbers/protocol-numbers.xhtml). (Optional)
448         :param tcpflg_val: TCP flags value. (Optional)
449         :param tcpflg_mask: TCP flags mask. (Optional)
450         :type node: dict
451         :type acl_idx: int
452         :type ip_ver: str
453         :type action: str
454         :type src: str
455         :type dst: str
456         :type sport: str or int
457         :type dport: str or int
458         :type proto: int
459         :type tcpflg_val: int
460         :type tcpflg_mask: int
461         :raises RuntimeError: If unable to add or replace ACL.
462         """
463         acl_idx = '{0}'.format(acl_idx) if acl_idx else ''
464
465         src = 'src {0}'.format(src) if src else ''
466
467         dst = 'dst {0}'.format(dst) if dst else ''
468
469         sport = 'sport {0}'.format(sport) if sport else ''
470
471         dport = 'dport {0}'.format(dport) if dport else ''
472
473         proto = 'proto {0}'.format(proto) if proto else ''
474
475         tcpflags = 'tcpflags {0} {1}'.format(tcpflg_val, tcpflg_mask) \
476             if tcpflg_val and tcpflg_mask else ''
477
478         try:
479             with VatTerminal(node, json_param=False) as vat:
480                 vat.vat_terminal_exec_cmd_from_template(
481                     "acl_plugin/acl_add_replace.vat", acl_idx=acl_idx,
482                     ip_ver=ip_ver, action=action, src=src, dst=dst, sport=sport,
483                     dport=dport, proto=proto, tcpflags=tcpflags)
484         except RuntimeError:
485             raise RuntimeError("Adding or replacing of ACL failed on "
486                                "node {0}".format(node['host']))
487
488     @staticmethod
489     def add_replace_acl_multi_entries(node, acl_idx=None, rules=None):
490         """Add a new ACL or replace the existing one. To replace an existing
491         ACL, pass the ID of this ACL.
492
493         :param node: VPP node to set ACL on.
494         :param acl_idx: ID of ACL. (Optional)
495         :param rules: Required rules. (Optional)
496         :type node: dict
497         :type acl_idx: int
498         :type rules: str
499         :raises RuntimeError: If unable to add or replace ACL.
500         """
501         acl_idx = '{0}'.format(acl_idx) if acl_idx else ''
502
503         rules = '{0}'.format(rules) if rules else ''
504
505         try:
506             with VatTerminal(node, json_param=False) as vat:
507                 vat.vat_terminal_exec_cmd_from_template(
508                     "acl_plugin/acl_add_replace.vat", acl_idx=acl_idx,
509                     ip_ver=rules, action='', src='', dst='', sport='',
510                     dport='', proto='', tcpflags='')
511         except RuntimeError:
512             raise RuntimeError("Adding or replacing of ACL failed on "
513                                "node {0}".format(node['host']))
514
515     @staticmethod
516     def delete_acl(node, idx):
517         """Delete required ACL.
518
519         :param node: VPP node to delete ACL on.
520         :param idx: Index of ACL to be deleted.
521         :type node: dict
522         :type idx: int or str
523         :raises RuntimeError: If unable to delete ACL.
524         """
525         try:
526             with VatTerminal(node, json_param=False) as vat:
527                 vat.vat_terminal_exec_cmd_from_template(
528                     "acl_plugin/acl_delete.vat", idx=idx)
529         except RuntimeError:
530             raise RuntimeError("Deletion of ACL failed on node {0}".
531                                format(node['host']))
532
533     @staticmethod
534     def cli_show_acl(node, acl_idx=None):
535         """Show ACLs.
536
537         :param node: VPP node to show ACL on.
538         :param acl_idx: Index of ACL to be shown.
539         :type node: dict
540         :type acl_idx: int or str
541         :raises RuntimeError: If unable to delete ACL.
542         """
543         acl_idx = '{0}'.format(acl_idx) if acl_idx else ''
544
545         try:
546             with VatTerminal(node, json_param=False) as vat:
547                 vat.vat_terminal_exec_cmd_from_template(
548                     "acl_plugin/show_acl.vat", idx=acl_idx)
549         except RuntimeError:
550             raise RuntimeError("Failed to show ACL on node {0}".
551                                format(node['host']))
552
553     @staticmethod
554     def add_macip_acl(node, ip_ver="ipv4", action="permit", src_ip=None,
555                       src_mac=None, src_mac_mask=None):
556         """Add a new MACIP ACL.
557
558         :param node: VPP node to set MACIP ACL on.
559         :param ip_ver: IP version. (Optional)
560         :param action: ACL action. (Optional)
561         :param src_ip: Source IP in format IP/plen. (Optional)
562         :param src_mac: Source MAC address in format with colons. (Optional)
563         :param src_mac_mask: Source MAC address mask in format with colons.
564          00:00:00:00:00:00 is a wildcard mask. (Optional)
565         :type node: dict
566         :type ip_ver: str
567         :type action: str
568         :type src_ip: str
569         :type src_mac: str
570         :type src_mac_mask: str
571         :raises RuntimeError: If unable to add MACIP ACL.
572         """
573         src_ip = 'ip {0}'.format(src_ip) if src_ip else ''
574
575         src_mac = 'mac {0}'.format(src_mac) if src_mac else ''
576
577         src_mac_mask = 'mask {0}'.format(src_mac_mask) if src_mac_mask else ''
578
579         try:
580             with VatTerminal(node, json_param=False) as vat:
581                 vat.vat_terminal_exec_cmd_from_template(
582                     "acl_plugin/macip_acl_add.vat", ip_ver=ip_ver,
583                     action=action, src_ip=src_ip, src_mac=src_mac,
584                     src_mac_mask=src_mac_mask)
585         except RuntimeError:
586             raise RuntimeError("Adding of MACIP ACL failed on node {0}".
587                                format(node['host']))
588
589     @staticmethod
590     def add_macip_acl_multi_entries(node, rules=None):
591         """Add a new MACIP ACL.
592
593         :param node: VPP node to set MACIP ACL on.
594         :param rules: Required MACIP rules. (Optional)
595         :type node: dict
596         :type rules: str
597         :raises RuntimeError: If unable to add MACIP ACL.
598         """
599         rules = '{0}'.format(rules) if rules else ''
600
601         try:
602             with VatTerminal(node, json_param=False) as vat:
603                 vat.vat_terminal_exec_cmd_from_template(
604                     "acl_plugin/macip_acl_add.vat", ip_ver=rules, action='',
605                     src_ip='', src_mac='', src_mac_mask='')
606         except RuntimeError:
607             raise RuntimeError("Adding of MACIP ACL failed on node {0}".
608                                format(node['host']))
609
610     @staticmethod
611     def delete_macip_acl(node, idx):
612         """Delete required MACIP ACL.
613
614         :param node: VPP node to delete MACIP ACL on.
615         :param idx: Index of ACL to be deleted.
616         :type node: dict
617         :type idx: int or str
618         :raises RuntimeError: If unable to delete MACIP ACL.
619         """
620         try:
621             with VatTerminal(node, json_param=False) as vat:
622                 vat.vat_terminal_exec_cmd_from_template(
623                     "acl_plugin/macip_acl_delete.vat", idx=idx)
624         except RuntimeError:
625             raise RuntimeError("Deletion of MACIP ACL failed on node {0}".
626                                format(node['host']))
627
628     @staticmethod
629     def vpp_log_macip_acl_settings(node):
630         """Retrieve configured MACIP settings from the ACL plugin
631          and write to robot log.
632
633         :param node: VPP node.
634         :type node: dict
635         """
636         try:
637             VatExecutor.cmd_from_template(
638                 node, "acl_plugin/macip_acl_dump.vat")
639         except (ValueError, RuntimeError):
640             # Fails to parse JSON data in response, but it is still logged
641             pass
642
643     @staticmethod
644     def add_del_macip_acl_interface(node, interface, action, acl_idx):
645         """Apply/un-apply the MACIP ACL to/from a given interface.
646
647         :param node: VPP node to set MACIP ACL on.
648         :param interface: Interface name or sw_if_index.
649         :param action: Required action - add or del.
650         :param acl_idx: ACL index to be applied on the interface.
651         :type node: dict
652         :type interface: str or int
653         :type action: str
654         :type acl_idx: str or int
655         :raises RuntimeError: If unable to set MACIP ACL for the interface.
656         """
657         if isinstance(interface, basestring):
658             sw_if_index = Topology.get_interface_sw_index(node, interface)
659         else:
660             sw_if_index = interface
661
662         try:
663             with VatTerminal(node, json_param=False) as vat:
664                 vat.vat_terminal_exec_cmd_from_template(
665                     "acl_plugin/macip_acl_interface_add_del.vat",
666                     sw_if_index=sw_if_index, action=action, acl_idx=acl_idx)
667         except RuntimeError:
668             raise RuntimeError("Setting of MACIP ACL index for interface {0} "
669                                "failed on node {1}".
670                                format(interface, node['host']))
671
672     @staticmethod
673     def vpp_log_macip_acl_interface_assignment(node):
674         """Get interface list and associated MACIP ACLs and write to robot log.
675
676         :param node: VPP node.
677         :type node: dict
678         """
679         try:
680             VatExecutor.cmd_from_template(
681                 node, "acl_plugin/macip_acl_interface_get.vat", json_out=False)
682         except RuntimeError:
683             # Fails to parse response, but it is still logged
684             pass