3 # @author David Spence <dspence@brocade.com>
5 # (C) 2017 Brocade Communications Systems, Inc.
6 # 130 Holger Way, San Jose, CA 95134.
9 # Brocade, the B-wing symbol, Brocade Assurance, ADX, AnyIO, DCX, Fabric OS,
10 # FastIron, HyperEdge, ICX, MLX, MyBrocade, NetIron, OpenScript, VCS, VDX, and
11 # Vyatta are registered trademarks, and The Effortless Network and the On-Demand
12 # Data Center are trademarks of Brocade Communications Systems, Inc., in the
13 # United States and in other countries. Other brands and product names mentioned
14 # may be trademarks of others.
16 # Use of the software files and documentation is subject to license terms.
18 '''A CI test script for odl-jsonrpc.'''
21 from datetime import datetime
24 from argparse import ArgumentParser
29 class StreamFormatter(logging.Formatter):
30 '''Provide a custom timestamp for logging.'''
31 def formatTime(self, record, datefmt=None):
32 '''Return record time as a UTC timestamp (RFC 3339 Section 5.6).'''
33 return datetime.utcfromtimestamp(record.created).isoformat('T') + 'Z'
35 PARSE_ERROR_RESPONSE = json.dumps({
37 'error': {'code': -32700, 'message': 'Parse error'},
40 INVALID_REQUEST_RESPONSE = json.dumps({
42 'error': {'code': -32600, 'message': 'Invalid Request'},
46 class Service(object):
47 '''A service accepting JSON RPC Requests for the 'read' method.'''
48 def __init__(self, store, entity, path, value):
53 self._methods = {'read': self.read}
55 def _parse_request(request):
56 '''If `request` passes the most basic checks for a JSON RPC Request,
57 then return its 'id', 'method' and 'params'.
60 version = request['jsonrpc']
62 method = request['method']
63 params = request['params']
68 return (version, id_, method, params)
69 def read(self, store, entity, path):
70 '''The implementation of the `read` method for this test. If `store`,
71 `entity` and `path` do not match expected values, then return the
72 configured fixed value: otherwise raise :class:`ValueError`.
74 if self._store != store or self._entity != entity or self._path != path:
75 raise ValueError('unexpected param values')
77 def execute_json(self, string):
78 '''Execute an encoded JSON RPC Request from `string`. Return a 2-tuple
79 of (code, response), where code is process exit code and `response`
80 is an encoded JSON RPC Response. A zero exit code is returned if the
81 method implementation of this service was invoked successfully:
85 request = json.loads(string)
87 return (1, PARSE_ERROR_RESPONSE)
89 (version, id_, method, params) = self._parse_request(request)
91 return (2, INVALID_REQUEST_RESPONSE)
92 response = {'jsonrpc': version, 'id': id_}
94 ### assumes that params are supplied as a list for call by position
95 response['result'] = self._methods[method](*params) # pylint: disable=star-args
97 response['error'] = {'code': -32601, 'message': 'Method not found'}
100 response['error'] = {'code': -32602, 'message': 'Invalid params'}
102 except ValueError as exc:
103 response['error'] = {'code': -32603, 'message': 'Internal error'}
104 response['error']['data'] = str(exc)
108 return (code, json.dumps(response))
110 def init_logging(level):
111 '''Initialise the default logger at logging `level`.'''
112 logger = logging.getLogger()
113 logger.setLevel(level)
114 handler = logging.StreamHandler()
115 formatter = StreamFormatter(
116 fmt='%(asctime)s:%(levelname)s:%(name)s:%(message)s'
118 handler.setFormatter(formatter)
119 logger.addHandler(handler)
121 def json_or_string(value):
122 '''If `value` is a JSON-encoded string, then return the decoded value:
123 otherwise return `value` as a string.
126 return json.loads(value)
131 '''Run a ZMQ REP socket on `uri` for accepting a single JSON RPC Request.
133 To successfully invoke the 'read' method, the Request 'params' must be
134 [`store`, `entity`, `path`] with the exact same values as were specified
135 in the command line args. On success, a JSON RPC Response with 'result'
136 `value` will be returned: otherwise, a JSON RPC Response with 'error'
139 init_logging(logging.INFO)
141 aparser = ArgumentParser(description=main.__doc__)
142 aparser.add_argument('uri')
143 aparser.add_argument('store', type=json_or_string)
144 aparser.add_argument('entity', type=json_or_string)
145 aparser.add_argument('path', type=json_or_string)
146 aparser.add_argument('value', type=json_or_string)
147 args = aparser.parse_args()
149 service = Service(args.store, args.entity, args.path, args.value)
151 zock = zmq.Context().socket(zmq.REP) # pylint: disable=no-member
153 logging.info('ZMQ REP listening on %s', args.uri)
154 request = zock.recv()
155 logging.info('>%s', request)
156 (code, response) = service.execute_json(request)
157 logging.info('<%s', response)
159 logging.info('exiting with code %d', code)
163 if __name__ == '__main__':