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
30 class StreamFormatter(logging.Formatter):
31 """Provide a custom timestamp for logging."""
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"
38 PARSE_ERROR_RESPONSE = json.dumps(
39 {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": None}
41 INVALID_REQUEST_RESPONSE = json.dumps(
44 "error": {"code": -32600, "message": "Invalid Request"},
50 class Service(object):
51 """A service accepting JSON RPC Requests for the 'read' method."""
53 def __init__(self, store, entity, path, value):
58 self._methods = {"read": self.read}
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'.
66 version = request["jsonrpc"]
68 method = request["method"]
69 params = request["params"]
74 return (version, id_, method, params)
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`.
81 if self._store != store or self._entity != entity or self._path != path:
82 raise ValueError("unexpected param values")
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:
93 request = json.loads(string)
95 return (1, PARSE_ERROR_RESPONSE)
97 (version, id_, method, params) = self._parse_request(request)
99 return (2, INVALID_REQUEST_RESPONSE)
100 response = {"jsonrpc": version, "id": id_}
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
107 response["error"] = {"code": -32601, "message": "Method not found"}
110 response["error"] = {"code": -32602, "message": "Invalid params"}
112 except ValueError as exc:
113 response["error"] = {"code": -32603, "message": "Internal error"}
114 response["error"]["data"] = str(exc)
118 return (code, json.dumps(response))
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)
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.
136 return json.loads(value)
142 """Run a ZMQ REP socket on `uri` for accepting a single JSON RPC Request.
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'
150 init_logging(logging.INFO)
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()
160 service = Service(args.store, args.entity, args.path, args.value)
162 zock = zmq.Context().socket(zmq.REP) # pylint: disable=no-member
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)
170 logging.info("exiting with code %d", code)
175 if __name__ == "__main__":