go.ligato.io/vpp-agent/v3@v3.5.0/tests/robot/tools/vpp_api_executor.py (about) 1 #!/usr/bin/env python3 2 3 # Copyright (c) 2019 Cisco and/or its affiliates. 4 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # you may not use this file except in compliance with the License. 6 # You may obtain a copy of the License at: 7 # 8 # http://www.apache.org/licenses/LICENSE-2.0 9 # 10 # Unless required by applicable law or agreed to in writing, software 11 # distributed under the License is distributed on an "AS IS" BASIS, 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 16 import os 17 import sys 18 import json 19 import fnmatch 20 import argparse 21 import binascii 22 import ipaddress 23 24 from vpp_papi import VPP 25 26 vpp_json_dir_core = "/usr/share/vpp/api/core" 27 vpp_json_dir_plugins = "/usr/share/vpp/api/plugins" 28 29 30 def _convert_reply(api_r): 31 """Process API reply / a part of API reply for smooth converting to 32 JSON string. 33 34 It is used only with 'request' and 'dump' methods. 35 36 Apply binascii.hexlify() method for string values. 37 38 TODO: Implement complex solution to process of replies. 39 40 :param api_r: API reply. 41 :type api_r: Vpp_serializer reply object (named tuple) 42 :returns: Processed API reply / a part of API reply. 43 :rtype: dict 44 """ 45 unwanted_fields = ['count', 'index', 'context'] 46 47 def process_value(val): 48 """Process value. 49 50 :param val: Value to be processed. 51 :type val: object 52 :returns: Processed value. 53 :rtype: dict or str or int 54 """ 55 56 # with dict or list just recursively iterate through all elements 57 if isinstance(val, dict): 58 for val_k, val_v in val.items(): 59 val[str(val_k)] = process_value(val_v) 60 return val 61 elif isinstance(val, list): 62 for idx, val_l in enumerate(val): 63 val[idx] = process_value(val_l) 64 return val 65 # no processing for int 66 elif hasattr(val, '__int__'): 67 return int(val) 68 elif isinstance(val, bytes): 69 # if exactly 16 bytes it's probably an IP address 70 if len(val) == 16: 71 try: 72 # without context we don't know if it's IPv4 or IPv6, return both forms 73 ipv4 = ipaddress.IPv4Address(val[:4]) 74 ipv6 = ipaddress.IPv6Address(val) 75 return {"ipv4": str(ipv4), "ipv6": str(ipv6)} 76 except ipaddress.AddressValueError: 77 # maybe it's not an IP address after all 78 pass 79 elif len(val) in (6, 8): 80 # Probably a padded MAC address(8) or "Dmac, Smac, etc."??(6) 81 return val.hex() 82 83 # strip null byte padding from some fields, such as tag or name 84 while val.endswith(b"\x00"): 85 val = val[:-1] 86 return str(val, "ascii") 87 elif hasattr(val, '__str__'): 88 89 if "(" in repr(val): 90 # it's another vpp-internal object 91 item_dict = dict() 92 for item in dir(val): 93 if not item.startswith("_") and item not in unwanted_fields: 94 item_dict[item] = process_value(getattr(val, item)) 95 return item_dict 96 else: 97 # just a simple string 98 return str(val) 99 # Next handles parameters not supporting preferred integer or string 100 # representation to get it logged 101 elif hasattr(val, '__repr__'): 102 return repr(val) 103 else: 104 return val 105 106 reply_dict = dict() 107 reply_key = repr(api_r).split('(')[0] 108 reply_value = dict() 109 for item in dir(api_r): 110 if not item.startswith('_') and item not in unwanted_fields: 111 reply_value[item] = process_value(getattr(api_r, item)) 112 reply_dict[reply_key] = reply_value 113 return reply_dict 114 115 116 class VppApi(object): 117 def __init__(self): 118 self.vpp = None 119 120 jsonfiles = [] 121 for root, dirnames, filenames in os.walk(vpp_json_dir_core): 122 for filename in fnmatch.filter(filenames, '*.api.json'): 123 jsonfiles.append(os.path.join(vpp_json_dir_core, filename)) 124 for root, dirnames, filenames in os.walk(vpp_json_dir_plugins): 125 for filename in fnmatch.filter(filenames, '*.api.json'): 126 jsonfiles.append(os.path.join(vpp_json_dir_plugins, filename)) 127 128 self.vpp = VPP(jsonfiles) 129 130 def connect(self): 131 resp = self.vpp.connect("ligato-test-api") 132 if resp != 0: 133 raise RuntimeError("VPP papi connection failed.") 134 135 def list_capabilities(self): 136 print(dir(self.vpp.api)) 137 138 def disconnect(self): 139 resp = self.vpp.disconnect() 140 if resp != 0: 141 print("Warning: VPP papi disconnect failed.") 142 143 def show_version(self): 144 print(self.vpp.api.show_version()) 145 146 def process_json_request(self, args): 147 """Process the request/reply and dump classes of VPP API methods. 148 149 :param args: Command line arguments passed to VPP PAPI Provider. 150 :type args: ArgumentParser 151 :returns: JSON formatted string. 152 :rtype: str 153 :raises RuntimeError: If PAPI command error occurs. 154 """ 155 156 vpp = self.vpp 157 158 reply = list() 159 160 def process_value(val): 161 """Process value. 162 163 :param val: Value to be processed. 164 :type val: object 165 :returns: Processed value. 166 :rtype: dict or str or int 167 """ 168 if isinstance(val, dict): 169 for val_k, val_v in val.items(): 170 val[str(val_k)] = process_value(val_v) 171 return val 172 elif isinstance(val, list): 173 for idx, val_l in enumerate(val): 174 val[idx] = process_value(val_l) 175 return val 176 elif isinstance(val, str): 177 return binascii.unhexlify(val) 178 elif isinstance(val, int): 179 return val 180 else: 181 return str(val) 182 183 self.connect() 184 json_data = json.loads(args.data) 185 186 for data in json_data: 187 api_name = data['api_name'] 188 api_args_unicode = data['api_args'] 189 api_reply = dict(api_name=api_name) 190 api_args = dict() 191 for a_k, a_v in api_args_unicode.items(): 192 api_args[str(a_k)] = process_value(a_v) 193 try: 194 papi_fn = getattr(vpp.api, api_name) 195 rep = papi_fn(**api_args) 196 197 if isinstance(rep, list): 198 converted_reply = list() 199 for r in rep: 200 converted_reply.append(_convert_reply(r)) 201 else: 202 converted_reply = _convert_reply(rep) 203 204 api_reply['api_reply'] = converted_reply 205 reply.append(api_reply) 206 except (AttributeError, ValueError) as err: 207 vpp.disconnect() 208 raise RuntimeError('PAPI command {api}({args}) input error:\n{err}'. 209 format(api=api_name, 210 args=api_args, 211 err=repr(err))) 212 except Exception as err: 213 vpp.disconnect() 214 raise RuntimeError('PAPI command {api}({args}) error:\n{exc}'. 215 format(api=api_name, 216 args=api_args, 217 exc=repr(err))) 218 self.disconnect() 219 220 return json.dumps(reply) 221 222 223 def main(): 224 """Main function for the Python API provider. 225 """ 226 227 # The functions which process different types of VPP Python API methods. 228 229 parser = argparse.ArgumentParser( 230 formatter_class=argparse.RawDescriptionHelpFormatter, 231 description=__doc__) 232 parser.add_argument("-d", "--data", 233 required=True, 234 help="Data is a JSON string (list) containing API name(s)" 235 "and its/their input argument(s).") 236 237 args = parser.parse_args() 238 239 vpp = VppApi() 240 return VppApi.process_json_request(vpp, args) 241 242 243 if __name__ == '__main__': 244 sys.stdout.write(main()) 245 sys.stdout.flush() 246 sys.exit(0)