dce14b5668c6a7a0d8feb483de22a1b7c2594bc9
[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
30 class StreamFormatter(logging.Formatter):
31     """Provide a custom timestamp for logging."""
32
33     def formatTime(self, record, datefmt=None):
34         """Return record time as a UTC timestamp (RFC 3339 Section 5.6)."""
35         return datetime.utcfromtimestamp(record.created).isoformat("T") + "Z"
36
37
38 PARSE_ERROR_RESPONSE = json.dumps(
39     {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": None}
40 )
41 INVALID_REQUEST_RESPONSE = json.dumps(
42     {
43         "jsonrpc": "2.0",
44         "error": {"code": -32600, "message": "Invalid Request"},
45         "id": None,
46     }
47 )
48
49
50 class Service(object):
51     """A service accepting JSON RPC Requests for the 'read' method."""
52
53     def __init__(self, store, entity, path, value):
54         self._store = store
55         self._entity = entity
56         self._path = path
57         self._value = value
58         self._methods = {"read": self.read}
59
60     @staticmethod
61     def _parse_request(request):
62         """If `request` passes the most basic checks for a JSON RPC Request,
63            then return its 'id', 'method' and 'params'.
64         """
65         try:
66             version = request["jsonrpc"]
67             id_ = request["id"]
68             method = request["method"]
69             params = request["params"]
70         except KeyError:
71             raise ValueError()
72         if version != "2.0":
73             raise ValueError()
74         return (version, id_, method, params)
75
76     def read(self, store, entity, path):
77         """The implementation of the `read` method for this test. If `store`,
78            `entity` and `path` do not match expected values, then return the
79            configured fixed value: otherwise raise :class:`ValueError`.
80         """
81         if self._store != store or self._entity != entity or self._path != path:
82             raise ValueError("unexpected param values")
83         return self._value
84
85     def execute_json(self, string):
86         """Execute an encoded JSON RPC Request from `string`. Return a 2-tuple
87            of (code, response), where code is process exit code and `response`
88            is an encoded JSON RPC Response. A zero exit code is returned if the
89            method implementation of this service was invoked successfully:
90            non-zero otherwise.
91         """
92         try:
93             request = json.loads(string)
94         except ValueError:
95             return (1, PARSE_ERROR_RESPONSE)
96         try:
97             (version, id_, method, params) = self._parse_request(request)
98         except ValueError:
99             return (2, INVALID_REQUEST_RESPONSE)
100         response = {"jsonrpc": version, "id": id_}
101         try:
102             # assumes that params are supplied as a list for call by position
103             response["result"] = self._methods[method](
104                 params['store'], params['entity'], params['path']
105             )  # pylint: disable=star-args
106         except KeyError:
107             response["error"] = {"code": -32601, "message": "Method not found"}
108             code = 3
109         except TypeError:
110             response["error"] = {"code": -32602, "message": "Invalid params"}
111             code = 4
112         except ValueError as exc:
113             response["error"] = {"code": -32603, "message": "Internal error"}
114             response["error"]["data"] = str(exc)
115             code = 5
116         else:
117             code = 0
118         return (code, json.dumps(response))
119
120
121 def init_logging(level):
122     """Initialise the default logger at logging `level`."""
123     logger = logging.getLogger()
124     logger.setLevel(level)
125     handler = logging.StreamHandler()
126     formatter = StreamFormatter(fmt="%(asctime)s:%(levelname)s:%(name)s:%(message)s")
127     handler.setFormatter(formatter)
128     logger.addHandler(handler)
129
130
131 def json_or_string(value):
132     """If `value` is a JSON-encoded string, then return the decoded value:
133        otherwise return `value` as a string.
134     """
135     try:
136         return json.loads(value)
137     except ValueError:
138         return str(value)
139
140
141 def main():
142     """Run a ZMQ REP socket on `uri` for accepting a single JSON RPC Request.
143
144        To successfully invoke the 'read' method, the Request 'params' must be
145        [`store`, `entity`, `path`] with the exact same values as were specified
146        in the command line args. On success, a JSON RPC Response with 'result'
147        `value` will be returned: otherwise, a JSON RPC Response with 'error'
148        will be returned.
149     """
150     init_logging(logging.INFO)
151
152     aparser = ArgumentParser(description=main.__doc__)
153     aparser.add_argument("uri")
154     aparser.add_argument("store", type=json_or_string)
155     aparser.add_argument("entity", type=json_or_string)
156     aparser.add_argument("path", type=json_or_string)
157     aparser.add_argument("value", type=json_or_string)
158     args = aparser.parse_args()
159
160     service = Service(args.store, args.entity, args.path, args.value)
161
162     zock = zmq.Context().socket(zmq.REP)  # pylint: disable=no-member
163     zock.bind(args.uri)
164     logging.info("ZMQ REP listening on %s", args.uri)
165     request = zock.recv()
166     logging.info(">%s", request)
167     (code, response) = service.execute_json(request)
168     logging.info("<%s", response)
169     zock.send(response)
170     logging.info("exiting with code %d", code)
171     zock.close()
172     sys.exit(code)
173
174
175 if __name__ == "__main__":
176     main()