blob: fba9ce4d142a1c2fee5be9fb9c231bdf435aca66 [file] [log] [blame]
Matteo Scandolod2044a42017-08-07 16:08:28 -07001# Copyright 2017-present Open Networking Foundation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Zack Williams5c2ea232019-01-30 15:23:01 -070015from __future__ import absolute_import, print_function
16
Scott Bakere72e7612017-02-20 10:07:09 -080017import argparse
Scott Baker96b995a2017-02-15 16:21:12 -080018import base64
19import functools
Zack Williams5c2ea232019-01-30 15:23:01 -070020import inspect
Scott Baker96b995a2017-02-15 16:21:12 -080021import os
Scott Baker96b995a2017-02-15 16:21:12 -080022import sys
Scott Baker96b995a2017-02-15 16:21:12 -080023
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +020024from google.protobuf.empty_pb2 import Empty
Zack Williams045b63d2019-01-22 16:30:57 -070025
Zack Williams5c2ea232019-01-30 15:23:01 -070026import grpc
27from grpc import (composite_channel_credentials, metadata_call_credentials,
28 ssl_channel_credentials)
Zack Williams045b63d2019-01-22 16:30:57 -070029
Zack Williams5c2ea232019-01-30 15:23:01 -070030from twisted.internet import reactor
Matteo Scandoloe3d2f262018-06-05 17:45:39 -070031from xosconfig import Config
Zack Williams5c2ea232019-01-30 15:23:01 -070032
33from xosapi import orm
34import xosapi.chameleon_client.grpc_client as chameleon_client
Zack Williams045b63d2019-01-22 16:30:57 -070035
Matteo Scandoloe3d2f262018-06-05 17:45:39 -070036from multistructlog import create_logger
Zack Williams045b63d2019-01-22 16:30:57 -070037log = create_logger(Config().get("logging"))
Matteo Scandoloe3d2f262018-06-05 17:45:39 -070038
Zack Williams5c2ea232019-01-30 15:23:01 -070039currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
40sys.path = [currentdir] + sys.path
41
Zack Williams045b63d2019-01-22 16:30:57 -070042SERVER_CA = "/usr/local/share/ca-certificates/local_certs.crt"
Matteo Scandoloe3d2f262018-06-05 17:45:39 -070043
Scott Baker96b995a2017-02-15 16:21:12 -080044
45class UsernamePasswordCallCredentials(grpc.AuthMetadataPlugin):
Zack Williams045b63d2019-01-22 16:30:57 -070046 """Metadata wrapper for raw access token credentials."""
47
48 def __init__(self, username, password):
Scott Baker96b995a2017-02-15 16:21:12 -080049 self._username = username
50 self._password = password
Zack Williams045b63d2019-01-22 16:30:57 -070051
52 def __call__(self, context, callback):
53 basic_auth = "Basic %s" % base64.b64encode(
54 "%s:%s" % (self._username, self._password)
55 )
56 metadata = (("authorization", basic_auth),)
Scott Baker96b995a2017-02-15 16:21:12 -080057 callback(metadata, None)
58
Zack Williams045b63d2019-01-22 16:30:57 -070059
Scott Baker96b995a2017-02-15 16:21:12 -080060class SessionIdCallCredentials(grpc.AuthMetadataPlugin):
Zack Williams045b63d2019-01-22 16:30:57 -070061 """Metadata wrapper for raw access token credentials."""
62
63 def __init__(self, sessionid):
Scott Baker96b995a2017-02-15 16:21:12 -080064 self._sessionid = sessionid
Zack Williams045b63d2019-01-22 16:30:57 -070065
66 def __call__(self, context, callback):
67 metadata = (("x-xossession", self._sessionid),)
Scott Baker96b995a2017-02-15 16:21:12 -080068 callback(metadata, None)
69
Zack Williams045b63d2019-01-22 16:30:57 -070070
Scott Baker96b995a2017-02-15 16:21:12 -080071class XOSClient(chameleon_client.GrpcClient):
72 # We layer our own reconnect_callback functionality so we can setup the
73 # ORM before calling reconnect_callback.
74
75 def set_reconnect_callback(self, reconnect_callback):
76 self.reconnect_callback2 = reconnect_callback
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +020077
Scott Baker96b995a2017-02-15 16:21:12 -080078 return self
79
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +020080 def load_convenience_methods(self):
81
Zack Williams045b63d2019-01-22 16:30:57 -070082 convenience_methods_dir = (
83 "/usr/local/lib/python2.7/dist-packages/xosapi/convenience/"
84 )
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +020085
86 try:
Matteo Scandolo5dda1a32018-05-14 14:03:10 -070087 response = self.dynamicload.GetConvenienceMethods(Empty())
88
89 if response:
Zack Williams045b63d2019-01-22 16:30:57 -070090 log.info(
91 "Loading convenience methods",
92 methods=[m.filename for m in response.convenience_methods],
93 )
Matteo Scandolo5dda1a32018-05-14 14:03:10 -070094
95 for cm in response.convenience_methods:
Matteo Scandoloe3d2f262018-06-05 17:45:39 -070096 log.debug("Saving convenience method", method=cm.filename)
Matteo Scandolo5dda1a32018-05-14 14:03:10 -070097 save_path = os.path.join(convenience_methods_dir, cm.filename)
Zack Williams5c2ea232019-01-30 15:23:01 -070098 open(save_path, "w").write(cm.contents)
Matteo Scandolo5dda1a32018-05-14 14:03:10 -070099 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700100 log.exception(
101 "Cannot load convenience methods, restarting the synchronzier"
102 )
103 os.execv(sys.executable, ["python"] + sys.argv)
Matteo Scandolo5dda1a32018-05-14 14:03:10 -0700104
Zack Williams045b63d2019-01-22 16:30:57 -0700105 except grpc._channel._Rendezvous as e:
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +0200106 code = e.code()
107 if code == grpc.StatusCode.UNAVAILABLE:
108 # NOTE if the core is not available, restart the synchronizer
Zack Williams045b63d2019-01-22 16:30:57 -0700109 os.execv(sys.executable, ["python"] + sys.argv)
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +0200110
Scott Baker96b995a2017-02-15 16:21:12 -0800111 def reconnected(self):
Zack Williams045b63d2019-01-22 16:30:57 -0700112 for api in ["modeldefs", "utility", "xos", "dynamicload"]:
Scott Baker96b995a2017-02-15 16:21:12 -0800113 pb2_file_name = os.path.join(self.work_dir, api + "_pb2.py")
114 pb2_grpc_file_name = os.path.join(self.work_dir, api + "_pb2_grpc.py")
115
116 if os.path.exists(pb2_file_name) and os.path.exists(pb2_grpc_file_name):
117 orig_sys_path = sys.path
118 try:
119 sys.path.append(self.work_dir)
120 m_protos = __import__(api + "_pb2")
Zack Williams6bdd3ea2018-03-26 15:21:05 -0700121 # reload(m_protos)
Scott Baker96b995a2017-02-15 16:21:12 -0800122 m_grpc = __import__(api + "_pb2_grpc")
Zack Williams6bdd3ea2018-03-26 15:21:05 -0700123 # reload(m_grpc)
Scott Baker96b995a2017-02-15 16:21:12 -0800124 finally:
125 sys.path = orig_sys_path
126
Zack Williams045b63d2019-01-22 16:30:57 -0700127 stub_class = getattr(m_grpc, api + "Stub")
Scott Baker96b995a2017-02-15 16:21:12 -0800128
129 setattr(self, api, stub_class(self.channel))
Zack Williams045b63d2019-01-22 16:30:57 -0700130 setattr(self, api + "_pb2", m_protos)
Scott Baker96b995a2017-02-15 16:21:12 -0800131 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700132 print("failed to locate api", api, file=sys.stderr)
Scott Baker96b995a2017-02-15 16:21:12 -0800133
134 if hasattr(self, "xos"):
Scott Bakerb96ba432018-02-26 09:53:48 -0800135 self.xos_orm = orm.ORMStub(self.xos, self.xos_pb2, "xos")
Scott Baker96b995a2017-02-15 16:21:12 -0800136
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +0200137 # ask the core for the convenience methods
138 self.load_convenience_methods()
139
140 # Load convenience methods after reconnect
141 orm.import_convenience_methods()
142
Scott Baker96b995a2017-02-15 16:21:12 -0800143 if self.reconnect_callback2:
144 self.reconnect_callback2()
145
146
147class InsecureClient(XOSClient):
Zack Williams045b63d2019-01-22 16:30:57 -0700148 def __init__(
149 self,
150 consul_endpoint=None,
151 work_dir="/tmp/xos_grpc_protos",
152 endpoint="localhost:50055",
153 reconnect_callback=None,
154 ):
155 super(InsecureClient, self).__init__(
156 consul_endpoint, work_dir, endpoint, self.reconnected
157 )
Scott Baker96b995a2017-02-15 16:21:12 -0800158
159 self.reconnect_callback2 = reconnect_callback
160
Zack Williams045b63d2019-01-22 16:30:57 -0700161
Scott Baker96b995a2017-02-15 16:21:12 -0800162class SecureClient(XOSClient):
Zack Williams045b63d2019-01-22 16:30:57 -0700163 def __init__(
164 self,
165 consul_endpoint=None,
166 work_dir="/tmp/xos_grpc_protos",
167 endpoint="localhost:50055",
168 reconnect_callback=None,
169 cacert=SERVER_CA,
170 username=None,
171 password=None,
172 sessionid=None,
173 ):
174 server_ca = open(cacert, "r").read()
175 if sessionid:
Scott Baker96b995a2017-02-15 16:21:12 -0800176 call_creds = metadata_call_credentials(SessionIdCallCredentials(sessionid))
177 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700178 call_creds = metadata_call_credentials(
179 UsernamePasswordCallCredentials(username, password)
180 )
Scott Baker96b995a2017-02-15 16:21:12 -0800181 chan_creds = ssl_channel_credentials(server_ca)
182 chan_creds = composite_channel_credentials(chan_creds, call_creds)
183
Zack Williams045b63d2019-01-22 16:30:57 -0700184 super(SecureClient, self).__init__(
185 consul_endpoint, work_dir, endpoint, self.reconnected, chan_creds
186 )
Scott Baker96b995a2017-02-15 16:21:12 -0800187
188 self.reconnect_callback2 = reconnect_callback
189
Zack Williams045b63d2019-01-22 16:30:57 -0700190
Scott Baker96b995a2017-02-15 16:21:12 -0800191# -----------------------------------------------------------------------------
Scott Bakere72e7612017-02-20 10:07:09 -0800192# Wrappers for easy setup for test cases, etc
193# -----------------------------------------------------------------------------
194
Zack Williams045b63d2019-01-22 16:30:57 -0700195
Scott Bakere72e7612017-02-20 10:07:09 -0800196def parse_args():
197 parser = argparse.ArgumentParser()
198
Zack Williams045b63d2019-01-22 16:30:57 -0700199 defs = {
200 "grpc_insecure_endpoint": "xos-core.cord.lab:50055",
201 "grpc_secure_endpoint": "xos-core.cord.lab:50051",
202 "config": "/opt/xos/config.yml",
203 }
Scott Bakere72e7612017-02-20 10:07:09 -0800204
Zack Williams045b63d2019-01-22 16:30:57 -0700205 _help = "Path to the config file (default: %s)" % defs["config"]
Scott Bakere72e7612017-02-20 10:07:09 -0800206 parser.add_argument(
Zack Williams045b63d2019-01-22 16:30:57 -0700207 "-C",
208 "--config",
209 dest="config",
210 action="store",
211 default=defs["config"],
212 help=_help,
213 )
Scott Bakere72e7612017-02-20 10:07:09 -0800214
Zack Williams045b63d2019-01-22 16:30:57 -0700215 _help = (
216 "gRPC insecure end-point to connect to. It is a direct",
217 ". (default: %s" % defs["grpc_insecure_endpoint"],
218 )
219 parser.add_argument(
220 "-G",
221 "--grpc-insecure-endpoint",
222 dest="grpc_insecure_endpoint",
223 action="store",
224 default=defs["grpc_insecure_endpoint"],
225 help=_help,
226 )
Scott Bakere72e7612017-02-20 10:07:09 -0800227
Zack Williams045b63d2019-01-22 16:30:57 -0700228 _help = (
229 "gRPC secure end-point to connect to. It is a direct",
230 ". (default: %s" % defs["grpc_secure_endpoint"],
231 )
232 parser.add_argument(
233 "-S",
234 "--grpc-secure-endpoint",
235 dest="grpc_secure_endpoint",
236 action="store",
237 default=defs["grpc_secure_endpoint"],
238 help=_help,
239 )
Scott Bakere72e7612017-02-20 10:07:09 -0800240
Zack Williams045b63d2019-01-22 16:30:57 -0700241 parser.add_argument(
242 "-u", "--username", dest="username", action="store", default=None, help=_help
243 )
Scott Bakere72e7612017-02-20 10:07:09 -0800244
Zack Williams045b63d2019-01-22 16:30:57 -0700245 parser.add_argument(
246 "-p", "--password", dest="password", action="store", default=None, help=_help
247 )
Scott Bakere72e7612017-02-20 10:07:09 -0800248
Zack Williams045b63d2019-01-22 16:30:57 -0700249 _help = "omit startup banner log lines"
250 parser.add_argument(
251 "-n",
252 "--no-banner",
253 dest="no_banner",
254 action="store_true",
255 default=False,
256 help=_help,
257 )
Scott Bakere72e7612017-02-20 10:07:09 -0800258
259 _help = "suppress debug and info logs"
Zack Williams045b63d2019-01-22 16:30:57 -0700260 parser.add_argument("-q", "--quiet", dest="quiet", action="count", help=_help)
Scott Bakere72e7612017-02-20 10:07:09 -0800261
Zack Williams045b63d2019-01-22 16:30:57 -0700262 _help = "enable verbose logging"
263 parser.add_argument("-v", "--verbose", dest="verbose", action="count", help=_help)
Scott Bakere72e7612017-02-20 10:07:09 -0800264
265 args = parser.parse_args()
266
267 return args
268
Sapan Bhatia51885532017-08-29 01:55:39 -0400269
Scott Bakere72e7612017-02-20 10:07:09 -0800270def coreclient_reconnect(client, reconnect_callback, *args, **kwargs):
271 global coreapi
272
273 coreapi = coreclient.xos_orm
274
275 if reconnect_callback:
276 reconnect_callback(*args, **kwargs)
277
278 reactor.stop()
279
Zack Williams045b63d2019-01-22 16:30:57 -0700280
Scott Bakere72e7612017-02-20 10:07:09 -0800281def start_api(reconnect_callback, *args, **kwargs):
282 global coreclient
283
284 if kwargs.get("username", None):
285 coreclient = SecureClient(*args, **kwargs)
286 else:
287 coreclient = InsecureClient(*args, **kwargs)
288
Zack Williams045b63d2019-01-22 16:30:57 -0700289 coreclient.set_reconnect_callback(
290 functools.partial(coreclient_reconnect, coreclient, reconnect_callback)
291 )
Scott Bakere72e7612017-02-20 10:07:09 -0800292 coreclient.start()
293
294 reactor.run()
295
Zack Williams045b63d2019-01-22 16:30:57 -0700296
Scott Bakere72e7612017-02-20 10:07:09 -0800297def start_api_parseargs(reconnect_callback):
Scott Baker95f7d952017-03-09 10:04:26 -0800298 """ This function is an entrypoint for tests and other simple programs to
299 setup the API and get a callback when the API is ready.
300 """
301
Scott Bakere72e7612017-02-20 10:07:09 -0800302 args = parse_args()
303
304 if args.username:
Zack Williams045b63d2019-01-22 16:30:57 -0700305 start_api(
306 reconnect_callback,
307 endpoint=args.grpc_secure_endpoint,
308 username=args.username,
309 password=args.password,
310 )
Scott Bakere72e7612017-02-20 10:07:09 -0800311 else:
312 start_api(reconnect_callback, endpoint=args.grpc_insecure_endpoint)
313
314
Scott Bakere72e7612017-02-20 10:07:09 -0800315# -----------------------------------------------------------------------------
Scott Baker96b995a2017-02-15 16:21:12 -0800316# Self test
317# -----------------------------------------------------------------------------
318
Zack Williams045b63d2019-01-22 16:30:57 -0700319
Scott Baker96b995a2017-02-15 16:21:12 -0800320def insecure_callback(client):
Zack Williams045b63d2019-01-22 16:30:57 -0700321 print("insecure self_test start")
322 print(client.xos_orm.User.objects.all())
323 print("insecure self_test done")
Scott Baker96b995a2017-02-15 16:21:12 -0800324
325 # now start the next test
326 client.stop()
327 reactor.callLater(0, start_secure_test)
328
Zack Williams045b63d2019-01-22 16:30:57 -0700329
Scott Baker96b995a2017-02-15 16:21:12 -0800330def start_insecure_test():
Matteo Scandoloe3d2f262018-06-05 17:45:39 -0700331 client = InsecureClient(endpoint="xos-core:50055")
Scott Baker96b995a2017-02-15 16:21:12 -0800332 client.set_reconnect_callback(functools.partial(insecure_callback, client))
333 client.start()
334
Zack Williams045b63d2019-01-22 16:30:57 -0700335
Scott Baker96b995a2017-02-15 16:21:12 -0800336def secure_callback(client):
Zack Williams045b63d2019-01-22 16:30:57 -0700337 print("secure self_test start")
338 print(client.xos_orm.User.objects.all())
339 print("secure self_test done")
Scott Baker96b995a2017-02-15 16:21:12 -0800340 reactor.stop()
341
Zack Williams045b63d2019-01-22 16:30:57 -0700342
Scott Baker96b995a2017-02-15 16:21:12 -0800343def start_secure_test():
Zack Williams045b63d2019-01-22 16:30:57 -0700344 client = SecureClient(
345 endpoint="xos-core:50051", username="admin@opencord.org", password="letmein"
346 )
Scott Baker96b995a2017-02-15 16:21:12 -0800347 client.set_reconnect_callback(functools.partial(secure_callback, client))
348 client.start()
349
Zack Williams045b63d2019-01-22 16:30:57 -0700350
Scott Baker96b995a2017-02-15 16:21:12 -0800351def main():
352 reactor.callLater(0, start_insecure_test)
353
354 reactor.run()
355
Scott Baker96b995a2017-02-15 16:21:12 -0800356
Zack Williams045b63d2019-01-22 16:30:57 -0700357if __name__ == "__main__":
358 main()