blob: 13e969df99fa996012a252143de61be2f5ad8cee [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 Williams045b63d2019-01-22 16:30:57 -070015from __future__ import print_function
Scott Bakere72e7612017-02-20 10:07:09 -080016import argparse
Scott Baker96b995a2017-02-15 16:21:12 -080017import base64
18import functools
19import grpc
20import orm
21import os
Scott Baker96b995a2017-02-15 16:21:12 -080022import sys
Scott Baker96b995a2017-02-15 16:21:12 -080023
24from twisted.internet import reactor
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +020025from google.protobuf.empty_pb2 import Empty
Zack Williams045b63d2019-01-22 16:30:57 -070026from grpc import (
27 metadata_call_credentials,
28 composite_channel_credentials,
29 ssl_channel_credentials,
30)
31
32# fix up sys.path for chameleon
33import inspect
34
35currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
36sys.path = [currentdir] + sys.path
Scott Baker96b995a2017-02-15 16:21:12 -080037
Matteo Scandoloe3d2f262018-06-05 17:45:39 -070038from xosconfig import Config
Zack Williams045b63d2019-01-22 16:30:57 -070039import chameleon.grpc_client.grpc_client as chameleon_client
40
Matteo Scandoloe3d2f262018-06-05 17:45:39 -070041from multistructlog import create_logger
Zack Williams045b63d2019-01-22 16:30:57 -070042log = create_logger(Config().get("logging"))
Matteo Scandoloe3d2f262018-06-05 17:45:39 -070043
Zack Williams045b63d2019-01-22 16:30:57 -070044SERVER_CA = "/usr/local/share/ca-certificates/local_certs.crt"
Matteo Scandoloe3d2f262018-06-05 17:45:39 -070045
Scott Baker96b995a2017-02-15 16:21:12 -080046
47class UsernamePasswordCallCredentials(grpc.AuthMetadataPlugin):
Zack Williams045b63d2019-01-22 16:30:57 -070048 """Metadata wrapper for raw access token credentials."""
49
50 def __init__(self, username, password):
Scott Baker96b995a2017-02-15 16:21:12 -080051 self._username = username
52 self._password = password
Zack Williams045b63d2019-01-22 16:30:57 -070053
54 def __call__(self, context, callback):
55 basic_auth = "Basic %s" % base64.b64encode(
56 "%s:%s" % (self._username, self._password)
57 )
58 metadata = (("authorization", basic_auth),)
Scott Baker96b995a2017-02-15 16:21:12 -080059 callback(metadata, None)
60
Zack Williams045b63d2019-01-22 16:30:57 -070061
Scott Baker96b995a2017-02-15 16:21:12 -080062class SessionIdCallCredentials(grpc.AuthMetadataPlugin):
Zack Williams045b63d2019-01-22 16:30:57 -070063 """Metadata wrapper for raw access token credentials."""
64
65 def __init__(self, sessionid):
Scott Baker96b995a2017-02-15 16:21:12 -080066 self._sessionid = sessionid
Zack Williams045b63d2019-01-22 16:30:57 -070067
68 def __call__(self, context, callback):
69 metadata = (("x-xossession", self._sessionid),)
Scott Baker96b995a2017-02-15 16:21:12 -080070 callback(metadata, None)
71
Zack Williams045b63d2019-01-22 16:30:57 -070072
Scott Baker96b995a2017-02-15 16:21:12 -080073class XOSClient(chameleon_client.GrpcClient):
74 # We layer our own reconnect_callback functionality so we can setup the
75 # ORM before calling reconnect_callback.
76
77 def set_reconnect_callback(self, reconnect_callback):
78 self.reconnect_callback2 = reconnect_callback
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +020079
Scott Baker96b995a2017-02-15 16:21:12 -080080 return self
81
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +020082 def load_convenience_methods(self):
83
Zack Williams045b63d2019-01-22 16:30:57 -070084 convenience_methods_dir = (
85 "/usr/local/lib/python2.7/dist-packages/xosapi/convenience/"
86 )
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +020087
88 try:
Matteo Scandolo5dda1a32018-05-14 14:03:10 -070089 response = self.dynamicload.GetConvenienceMethods(Empty())
90
91 if response:
Zack Williams045b63d2019-01-22 16:30:57 -070092 log.info(
93 "Loading convenience methods",
94 methods=[m.filename for m in response.convenience_methods],
95 )
Matteo Scandolo5dda1a32018-05-14 14:03:10 -070096
97 for cm in response.convenience_methods:
Matteo Scandoloe3d2f262018-06-05 17:45:39 -070098 log.debug("Saving convenience method", method=cm.filename)
Matteo Scandolo5dda1a32018-05-14 14:03:10 -070099 save_path = os.path.join(convenience_methods_dir, cm.filename)
100 file(save_path, "w").write(cm.contents)
101 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700102 log.exception(
103 "Cannot load convenience methods, restarting the synchronzier"
104 )
105 os.execv(sys.executable, ["python"] + sys.argv)
Matteo Scandolo5dda1a32018-05-14 14:03:10 -0700106
Zack Williams045b63d2019-01-22 16:30:57 -0700107 except grpc._channel._Rendezvous as e:
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +0200108 code = e.code()
109 if code == grpc.StatusCode.UNAVAILABLE:
110 # NOTE if the core is not available, restart the synchronizer
Zack Williams045b63d2019-01-22 16:30:57 -0700111 os.execv(sys.executable, ["python"] + sys.argv)
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +0200112
Scott Baker96b995a2017-02-15 16:21:12 -0800113 def reconnected(self):
Zack Williams045b63d2019-01-22 16:30:57 -0700114 for api in ["modeldefs", "utility", "xos", "dynamicload"]:
Scott Baker96b995a2017-02-15 16:21:12 -0800115 pb2_file_name = os.path.join(self.work_dir, api + "_pb2.py")
116 pb2_grpc_file_name = os.path.join(self.work_dir, api + "_pb2_grpc.py")
117
118 if os.path.exists(pb2_file_name) and os.path.exists(pb2_grpc_file_name):
119 orig_sys_path = sys.path
120 try:
121 sys.path.append(self.work_dir)
122 m_protos = __import__(api + "_pb2")
Zack Williams6bdd3ea2018-03-26 15:21:05 -0700123 # reload(m_protos)
Scott Baker96b995a2017-02-15 16:21:12 -0800124 m_grpc = __import__(api + "_pb2_grpc")
Zack Williams6bdd3ea2018-03-26 15:21:05 -0700125 # reload(m_grpc)
Scott Baker96b995a2017-02-15 16:21:12 -0800126 finally:
127 sys.path = orig_sys_path
128
Zack Williams045b63d2019-01-22 16:30:57 -0700129 stub_class = getattr(m_grpc, api + "Stub")
Scott Baker96b995a2017-02-15 16:21:12 -0800130
131 setattr(self, api, stub_class(self.channel))
Zack Williams045b63d2019-01-22 16:30:57 -0700132 setattr(self, api + "_pb2", m_protos)
Scott Baker96b995a2017-02-15 16:21:12 -0800133 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700134 print("failed to locate api", api, file=sys.stderr)
Scott Baker96b995a2017-02-15 16:21:12 -0800135
136 if hasattr(self, "xos"):
Scott Bakerb96ba432018-02-26 09:53:48 -0800137 self.xos_orm = orm.ORMStub(self.xos, self.xos_pb2, "xos")
Scott Baker96b995a2017-02-15 16:21:12 -0800138
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +0200139 # ask the core for the convenience methods
140 self.load_convenience_methods()
141
142 # Load convenience methods after reconnect
143 orm.import_convenience_methods()
144
Scott Baker96b995a2017-02-15 16:21:12 -0800145 if self.reconnect_callback2:
146 self.reconnect_callback2()
147
148
149class InsecureClient(XOSClient):
Zack Williams045b63d2019-01-22 16:30:57 -0700150 def __init__(
151 self,
152 consul_endpoint=None,
153 work_dir="/tmp/xos_grpc_protos",
154 endpoint="localhost:50055",
155 reconnect_callback=None,
156 ):
157 super(InsecureClient, self).__init__(
158 consul_endpoint, work_dir, endpoint, self.reconnected
159 )
Scott Baker96b995a2017-02-15 16:21:12 -0800160
161 self.reconnect_callback2 = reconnect_callback
162
Zack Williams045b63d2019-01-22 16:30:57 -0700163
Scott Baker96b995a2017-02-15 16:21:12 -0800164class SecureClient(XOSClient):
Zack Williams045b63d2019-01-22 16:30:57 -0700165 def __init__(
166 self,
167 consul_endpoint=None,
168 work_dir="/tmp/xos_grpc_protos",
169 endpoint="localhost:50055",
170 reconnect_callback=None,
171 cacert=SERVER_CA,
172 username=None,
173 password=None,
174 sessionid=None,
175 ):
176 server_ca = open(cacert, "r").read()
177 if sessionid:
Scott Baker96b995a2017-02-15 16:21:12 -0800178 call_creds = metadata_call_credentials(SessionIdCallCredentials(sessionid))
179 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700180 call_creds = metadata_call_credentials(
181 UsernamePasswordCallCredentials(username, password)
182 )
Scott Baker96b995a2017-02-15 16:21:12 -0800183 chan_creds = ssl_channel_credentials(server_ca)
184 chan_creds = composite_channel_credentials(chan_creds, call_creds)
185
Zack Williams045b63d2019-01-22 16:30:57 -0700186 super(SecureClient, self).__init__(
187 consul_endpoint, work_dir, endpoint, self.reconnected, chan_creds
188 )
Scott Baker96b995a2017-02-15 16:21:12 -0800189
190 self.reconnect_callback2 = reconnect_callback
191
Zack Williams045b63d2019-01-22 16:30:57 -0700192
Scott Baker96b995a2017-02-15 16:21:12 -0800193# -----------------------------------------------------------------------------
Scott Bakere72e7612017-02-20 10:07:09 -0800194# Wrappers for easy setup for test cases, etc
195# -----------------------------------------------------------------------------
196
Zack Williams045b63d2019-01-22 16:30:57 -0700197
Scott Bakere72e7612017-02-20 10:07:09 -0800198def parse_args():
199 parser = argparse.ArgumentParser()
200
Zack Williams045b63d2019-01-22 16:30:57 -0700201 defs = {
202 "grpc_insecure_endpoint": "xos-core.cord.lab:50055",
203 "grpc_secure_endpoint": "xos-core.cord.lab:50051",
204 "config": "/opt/xos/config.yml",
205 }
Scott Bakere72e7612017-02-20 10:07:09 -0800206
Zack Williams045b63d2019-01-22 16:30:57 -0700207 _help = "Path to the config file (default: %s)" % defs["config"]
Scott Bakere72e7612017-02-20 10:07:09 -0800208 parser.add_argument(
Zack Williams045b63d2019-01-22 16:30:57 -0700209 "-C",
210 "--config",
211 dest="config",
212 action="store",
213 default=defs["config"],
214 help=_help,
215 )
Scott Bakere72e7612017-02-20 10:07:09 -0800216
Zack Williams045b63d2019-01-22 16:30:57 -0700217 _help = (
218 "gRPC insecure end-point to connect to. It is a direct",
219 ". (default: %s" % defs["grpc_insecure_endpoint"],
220 )
221 parser.add_argument(
222 "-G",
223 "--grpc-insecure-endpoint",
224 dest="grpc_insecure_endpoint",
225 action="store",
226 default=defs["grpc_insecure_endpoint"],
227 help=_help,
228 )
Scott Bakere72e7612017-02-20 10:07:09 -0800229
Zack Williams045b63d2019-01-22 16:30:57 -0700230 _help = (
231 "gRPC secure end-point to connect to. It is a direct",
232 ". (default: %s" % defs["grpc_secure_endpoint"],
233 )
234 parser.add_argument(
235 "-S",
236 "--grpc-secure-endpoint",
237 dest="grpc_secure_endpoint",
238 action="store",
239 default=defs["grpc_secure_endpoint"],
240 help=_help,
241 )
Scott Bakere72e7612017-02-20 10:07:09 -0800242
Zack Williams045b63d2019-01-22 16:30:57 -0700243 parser.add_argument(
244 "-u", "--username", dest="username", action="store", default=None, help=_help
245 )
Scott Bakere72e7612017-02-20 10:07:09 -0800246
Zack Williams045b63d2019-01-22 16:30:57 -0700247 parser.add_argument(
248 "-p", "--password", dest="password", action="store", default=None, help=_help
249 )
Scott Bakere72e7612017-02-20 10:07:09 -0800250
Zack Williams045b63d2019-01-22 16:30:57 -0700251 _help = "omit startup banner log lines"
252 parser.add_argument(
253 "-n",
254 "--no-banner",
255 dest="no_banner",
256 action="store_true",
257 default=False,
258 help=_help,
259 )
Scott Bakere72e7612017-02-20 10:07:09 -0800260
261 _help = "suppress debug and info logs"
Zack Williams045b63d2019-01-22 16:30:57 -0700262 parser.add_argument("-q", "--quiet", dest="quiet", action="count", help=_help)
Scott Bakere72e7612017-02-20 10:07:09 -0800263
Zack Williams045b63d2019-01-22 16:30:57 -0700264 _help = "enable verbose logging"
265 parser.add_argument("-v", "--verbose", dest="verbose", action="count", help=_help)
Scott Bakere72e7612017-02-20 10:07:09 -0800266
267 args = parser.parse_args()
268
269 return args
270
Sapan Bhatia51885532017-08-29 01:55:39 -0400271
Scott Bakere72e7612017-02-20 10:07:09 -0800272def coreclient_reconnect(client, reconnect_callback, *args, **kwargs):
273 global coreapi
274
275 coreapi = coreclient.xos_orm
276
277 if reconnect_callback:
278 reconnect_callback(*args, **kwargs)
279
280 reactor.stop()
281
Zack Williams045b63d2019-01-22 16:30:57 -0700282
Scott Bakere72e7612017-02-20 10:07:09 -0800283def start_api(reconnect_callback, *args, **kwargs):
284 global coreclient
285
286 if kwargs.get("username", None):
287 coreclient = SecureClient(*args, **kwargs)
288 else:
289 coreclient = InsecureClient(*args, **kwargs)
290
Zack Williams045b63d2019-01-22 16:30:57 -0700291 coreclient.set_reconnect_callback(
292 functools.partial(coreclient_reconnect, coreclient, reconnect_callback)
293 )
Scott Bakere72e7612017-02-20 10:07:09 -0800294 coreclient.start()
295
296 reactor.run()
297
Zack Williams045b63d2019-01-22 16:30:57 -0700298
Scott Bakere72e7612017-02-20 10:07:09 -0800299def start_api_parseargs(reconnect_callback):
Scott Baker95f7d952017-03-09 10:04:26 -0800300 """ This function is an entrypoint for tests and other simple programs to
301 setup the API and get a callback when the API is ready.
302 """
303
Scott Bakere72e7612017-02-20 10:07:09 -0800304 args = parse_args()
305
306 if args.username:
Zack Williams045b63d2019-01-22 16:30:57 -0700307 start_api(
308 reconnect_callback,
309 endpoint=args.grpc_secure_endpoint,
310 username=args.username,
311 password=args.password,
312 )
Scott Bakere72e7612017-02-20 10:07:09 -0800313 else:
314 start_api(reconnect_callback, endpoint=args.grpc_insecure_endpoint)
315
316
Scott Bakere72e7612017-02-20 10:07:09 -0800317# -----------------------------------------------------------------------------
Scott Baker96b995a2017-02-15 16:21:12 -0800318# Self test
319# -----------------------------------------------------------------------------
320
Zack Williams045b63d2019-01-22 16:30:57 -0700321
Scott Baker96b995a2017-02-15 16:21:12 -0800322def insecure_callback(client):
Zack Williams045b63d2019-01-22 16:30:57 -0700323 print("insecure self_test start")
324 print(client.xos_orm.User.objects.all())
325 print("insecure self_test done")
Scott Baker96b995a2017-02-15 16:21:12 -0800326
327 # now start the next test
328 client.stop()
329 reactor.callLater(0, start_secure_test)
330
Zack Williams045b63d2019-01-22 16:30:57 -0700331
Scott Baker96b995a2017-02-15 16:21:12 -0800332def start_insecure_test():
Matteo Scandoloe3d2f262018-06-05 17:45:39 -0700333 client = InsecureClient(endpoint="xos-core:50055")
Scott Baker96b995a2017-02-15 16:21:12 -0800334 client.set_reconnect_callback(functools.partial(insecure_callback, client))
335 client.start()
336
Zack Williams045b63d2019-01-22 16:30:57 -0700337
Scott Baker96b995a2017-02-15 16:21:12 -0800338def secure_callback(client):
Zack Williams045b63d2019-01-22 16:30:57 -0700339 print("secure self_test start")
340 print(client.xos_orm.User.objects.all())
341 print("secure self_test done")
Scott Baker96b995a2017-02-15 16:21:12 -0800342 reactor.stop()
343
Zack Williams045b63d2019-01-22 16:30:57 -0700344
Scott Baker96b995a2017-02-15 16:21:12 -0800345def start_secure_test():
Zack Williams045b63d2019-01-22 16:30:57 -0700346 client = SecureClient(
347 endpoint="xos-core:50051", username="admin@opencord.org", password="letmein"
348 )
Scott Baker96b995a2017-02-15 16:21:12 -0800349 client.set_reconnect_callback(functools.partial(secure_callback, client))
350 client.start()
351
Zack Williams045b63d2019-01-22 16:30:57 -0700352
Scott Baker96b995a2017-02-15 16:21:12 -0800353def main():
354 reactor.callLater(0, start_insecure_test)
355
356 reactor.run()
357
Scott Baker96b995a2017-02-15 16:21:12 -0800358
Zack Williams045b63d2019-01-22 16:30:57 -0700359if __name__ == "__main__":
360 main()