Add dump of op:opendaylight-inventory
[integration/test.git] / csit / variables / jsonrpc / odl-jsonrpc-test-read
1 #!/usr/bin/env python
2
3 # @author David Spence <dspence@brocade.com>
4
5 # (C) 2017 Brocade Communications Systems, Inc.
6 # 130 Holger Way, San Jose, CA 95134.
7 # All rights reserved.
8 #
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.
15 #
16 # Use of the software files and documentation is subject to license terms.
17
18 '''A CI test script for odl-jsonrpc.'''
19
20 import logging
21 from datetime import datetime
22
23 import sys
24 from argparse import ArgumentParser
25
26 import json
27 import zmq
28
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'
34
35 PARSE_ERROR_RESPONSE = json.dumps({
36     'jsonrpc': '2.0',
37     'error': {'code': -32700, 'message': 'Parse error'},
38     'id': None,
39 })
40 INVALID_REQUEST_RESPONSE = json.dumps({
41     'jsonrpc': '2.0',
42     'error': {'code': -32600, 'message': 'Invalid Request'},
43     'id': None,
44 })
45
46 class Service(object):
47     '''A service accepting JSON RPC Requests for the 'read' method.'''
48     def __init__(self, store, entity, path, value):
49         self._store = store
50         self._entity = entity
51         self._path = path
52         self._value = value
53         self._methods = {'read': self.read}
54     @staticmethod
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'.
58         '''
59         try:
60             version = request['jsonrpc']
61             id_ = request['id']
62             method = request['method']
63             params = request['params']
64         except KeyError:
65             raise ValueError()
66         if version != '2.0':
67             raise ValueError()
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`.
73         '''
74         if self._store != store or self._entity != entity or self._path != path:
75             raise ValueError('unexpected param values')
76         return self._value
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:
82            non-zero otherwise.
83         '''
84         try:
85             request = json.loads(string)
86         except ValueError:
87             return (1, PARSE_ERROR_RESPONSE)
88         try:
89             (version, id_, method, params) = self._parse_request(request)
90         except ValueError:
91             return (2, INVALID_REQUEST_RESPONSE)
92         response = {'jsonrpc': version, 'id': id_}
93         try:
94             ### assumes that params are supplied as a list for call by position
95             response['result'] = self._methods[method](*params) # pylint: disable=star-args
96         except KeyError:
97             response['error'] = {'code': -32601, 'message': 'Method not found'}
98             code = 3
99         except TypeError:
100             response['error'] = {'code': -32602, 'message': 'Invalid params'}
101             code = 4
102         except ValueError as exc:
103             response['error'] = {'code': -32603, 'message': 'Internal error'}
104             response['error']['data'] = str(exc)
105             code = 5
106         else:
107             code = 0
108         return (code, json.dumps(response))
109
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'
117     )
118     handler.setFormatter(formatter)
119     logger.addHandler(handler)
120
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.
124     '''
125     try:
126         return json.loads(value)
127     except ValueError:
128         return str(value)
129
130 def main():
131     '''Run a ZMQ REP socket on `uri` for accepting a single JSON RPC Request.
132
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'
137        will be returned.
138     '''
139     init_logging(logging.INFO)
140
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()
148
149     service = Service(args.store, args.entity, args.path, args.value)
150
151     zock = zmq.Context().socket(zmq.REP) # pylint: disable=no-member
152     zock.bind(args.uri)
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)
158     zock.send(response)
159     logging.info('exiting with code %d', code)
160     zock.close()
161     sys.exit(code)
162
163 if __name__ == '__main__':
164     main()