blob: c3765528e4f6d030d25571d1513f1229d873229b [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
Scott Baker8203dc62019-03-14 16:16:19 -070080 def request_convenience_methods(self):
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +020081
Scott Baker8203dc62019-03-14 16:16:19 -070082 convenience_methods_dir = "/var/run/xosapi/convenience"
83 if not os.path.exists(convenience_methods_dir):
84 log.info("Creating convenience methods directory", convenience_methods_dir=convenience_methods_dir)
85 os.makedirs(convenience_methods_dir)
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +020086
87 try:
Matteo Scandolo5dda1a32018-05-14 14:03:10 -070088 response = self.dynamicload.GetConvenienceMethods(Empty())
89
90 if response:
Zack Williams045b63d2019-01-22 16:30:57 -070091 log.info(
Scott Baker8203dc62019-03-14 16:16:19 -070092 "Saving convenience methods",
Zack Williams045b63d2019-01-22 16:30:57 -070093 methods=[m.filename for m in response.convenience_methods],
94 )
Matteo Scandolo5dda1a32018-05-14 14:03:10 -070095
96 for cm in response.convenience_methods:
Matteo Scandoloe3d2f262018-06-05 17:45:39 -070097 log.debug("Saving convenience method", method=cm.filename)
Matteo Scandolo5dda1a32018-05-14 14:03:10 -070098 save_path = os.path.join(convenience_methods_dir, cm.filename)
Zack Williams5c2ea232019-01-30 15:23:01 -070099 open(save_path, "w").write(cm.contents)
Matteo Scandolo5dda1a32018-05-14 14:03:10 -0700100 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700101 log.exception(
102 "Cannot load convenience methods, restarting the synchronzier"
103 )
104 os.execv(sys.executable, ["python"] + sys.argv)
Matteo Scandolo5dda1a32018-05-14 14:03:10 -0700105
Zack Williams045b63d2019-01-22 16:30:57 -0700106 except grpc._channel._Rendezvous as e:
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +0200107 code = e.code()
108 if code == grpc.StatusCode.UNAVAILABLE:
109 # NOTE if the core is not available, restart the synchronizer
Zack Williams045b63d2019-01-22 16:30:57 -0700110 os.execv(sys.executable, ["python"] + sys.argv)
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +0200111
Scott Baker96b995a2017-02-15 16:21:12 -0800112 def reconnected(self):
Zack Williams045b63d2019-01-22 16:30:57 -0700113 for api in ["modeldefs", "utility", "xos", "dynamicload"]:
Scott Baker96b995a2017-02-15 16:21:12 -0800114 pb2_file_name = os.path.join(self.work_dir, api + "_pb2.py")
115 pb2_grpc_file_name = os.path.join(self.work_dir, api + "_pb2_grpc.py")
116
117 if os.path.exists(pb2_file_name) and os.path.exists(pb2_grpc_file_name):
118 orig_sys_path = sys.path
119 try:
120 sys.path.append(self.work_dir)
121 m_protos = __import__(api + "_pb2")
Zack Williams6bdd3ea2018-03-26 15:21:05 -0700122 # reload(m_protos)
Scott Baker96b995a2017-02-15 16:21:12 -0800123 m_grpc = __import__(api + "_pb2_grpc")
Zack Williams6bdd3ea2018-03-26 15:21:05 -0700124 # reload(m_grpc)
Scott Baker96b995a2017-02-15 16:21:12 -0800125 finally:
126 sys.path = orig_sys_path
127
Zack Williams045b63d2019-01-22 16:30:57 -0700128 stub_class = getattr(m_grpc, api + "Stub")
Scott Baker96b995a2017-02-15 16:21:12 -0800129
130 setattr(self, api, stub_class(self.channel))
Zack Williams045b63d2019-01-22 16:30:57 -0700131 setattr(self, api + "_pb2", m_protos)
Scott Baker96b995a2017-02-15 16:21:12 -0800132 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700133 print("failed to locate api", api, file=sys.stderr)
Scott Baker96b995a2017-02-15 16:21:12 -0800134
135 if hasattr(self, "xos"):
Scott Bakerb96ba432018-02-26 09:53:48 -0800136 self.xos_orm = orm.ORMStub(self.xos, self.xos_pb2, "xos")
Scott Baker96b995a2017-02-15 16:21:12 -0800137
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +0200138 # ask the core for the convenience methods
Scott Baker8203dc62019-03-14 16:16:19 -0700139 self.request_convenience_methods()
Matteo Scandolo10a2f3c2018-04-20 16:59:38 +0200140
141 # Load convenience methods after reconnect
142 orm.import_convenience_methods()
143
Scott Baker96b995a2017-02-15 16:21:12 -0800144 if self.reconnect_callback2:
145 self.reconnect_callback2()
146
147
148class InsecureClient(XOSClient):
Zack Williams045b63d2019-01-22 16:30:57 -0700149 def __init__(
150 self,
151 consul_endpoint=None,
152 work_dir="/tmp/xos_grpc_protos",
153 endpoint="localhost:50055",
154 reconnect_callback=None,
155 ):
156 super(InsecureClient, self).__init__(
157 consul_endpoint, work_dir, endpoint, self.reconnected
158 )
Scott Baker96b995a2017-02-15 16:21:12 -0800159
160 self.reconnect_callback2 = reconnect_callback
161
Zack Williams045b63d2019-01-22 16:30:57 -0700162
Scott Baker96b995a2017-02-15 16:21:12 -0800163class SecureClient(XOSClient):
Zack Williams045b63d2019-01-22 16:30:57 -0700164 def __init__(
165 self,
166 consul_endpoint=None,
167 work_dir="/tmp/xos_grpc_protos",
168 endpoint="localhost:50055",
169 reconnect_callback=None,
170 cacert=SERVER_CA,
171 username=None,
172 password=None,
173 sessionid=None,
174 ):
175 server_ca = open(cacert, "r").read()
176 if sessionid:
Scott Baker96b995a2017-02-15 16:21:12 -0800177 call_creds = metadata_call_credentials(SessionIdCallCredentials(sessionid))
178 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700179 call_creds = metadata_call_credentials(
180 UsernamePasswordCallCredentials(username, password)
181 )
Scott Baker96b995a2017-02-15 16:21:12 -0800182 chan_creds = ssl_channel_credentials(server_ca)
183 chan_creds = composite_channel_credentials(chan_creds, call_creds)
184
Zack Williams045b63d2019-01-22 16:30:57 -0700185 super(SecureClient, self).__init__(
186 consul_endpoint, work_dir, endpoint, self.reconnected, chan_creds
187 )
Scott Baker96b995a2017-02-15 16:21:12 -0800188
189 self.reconnect_callback2 = reconnect_callback
190
Zack Williams045b63d2019-01-22 16:30:57 -0700191
Scott Baker96b995a2017-02-15 16:21:12 -0800192# -----------------------------------------------------------------------------
Scott Bakere72e7612017-02-20 10:07:09 -0800193# Wrappers for easy setup for test cases, etc
194# -----------------------------------------------------------------------------
195
Zack Williams045b63d2019-01-22 16:30:57 -0700196
Scott Bakere72e7612017-02-20 10:07:09 -0800197def parse_args():
198 parser = argparse.ArgumentParser()
199
Zack Williams045b63d2019-01-22 16:30:57 -0700200 defs = {
201 "grpc_insecure_endpoint": "xos-core.cord.lab:50055",
202 "grpc_secure_endpoint": "xos-core.cord.lab:50051",
203 "config": "/opt/xos/config.yml",
204 }
Scott Bakere72e7612017-02-20 10:07:09 -0800205
Zack Williams045b63d2019-01-22 16:30:57 -0700206 _help = "Path to the config file (default: %s)" % defs["config"]
Scott Bakere72e7612017-02-20 10:07:09 -0800207 parser.add_argument(
Zack Williams045b63d2019-01-22 16:30:57 -0700208 "-C",
209 "--config",
210 dest="config",
211 action="store",
212 default=defs["config"],
213 help=_help,
214 )
Scott Bakere72e7612017-02-20 10:07:09 -0800215
Zack Williams045b63d2019-01-22 16:30:57 -0700216 _help = (
217 "gRPC insecure end-point to connect to. It is a direct",
218 ". (default: %s" % defs["grpc_insecure_endpoint"],
219 )
220 parser.add_argument(
221 "-G",
222 "--grpc-insecure-endpoint",
223 dest="grpc_insecure_endpoint",
224 action="store",
225 default=defs["grpc_insecure_endpoint"],
226 help=_help,
227 )
Scott Bakere72e7612017-02-20 10:07:09 -0800228
Zack Williams045b63d2019-01-22 16:30:57 -0700229 _help = (
230 "gRPC secure end-point to connect to. It is a direct",
231 ". (default: %s" % defs["grpc_secure_endpoint"],
232 )
233 parser.add_argument(
234 "-S",
235 "--grpc-secure-endpoint",
236 dest="grpc_secure_endpoint",
237 action="store",
238 default=defs["grpc_secure_endpoint"],
239 help=_help,
240 )
Scott Bakere72e7612017-02-20 10:07:09 -0800241
Zack Williams045b63d2019-01-22 16:30:57 -0700242 parser.add_argument(
243 "-u", "--username", dest="username", action="store", default=None, help=_help
244 )
Scott Bakere72e7612017-02-20 10:07:09 -0800245
Zack Williams045b63d2019-01-22 16:30:57 -0700246 parser.add_argument(
247 "-p", "--password", dest="password", action="store", default=None, help=_help
248 )
Scott Bakere72e7612017-02-20 10:07:09 -0800249
Zack Williams045b63d2019-01-22 16:30:57 -0700250 _help = "omit startup banner log lines"
251 parser.add_argument(
252 "-n",
253 "--no-banner",
254 dest="no_banner",
255 action="store_true",
256 default=False,
257 help=_help,
258 )
Scott Bakere72e7612017-02-20 10:07:09 -0800259
260 _help = "suppress debug and info logs"
Zack Williams045b63d2019-01-22 16:30:57 -0700261 parser.add_argument("-q", "--quiet", dest="quiet", action="count", help=_help)
Scott Bakere72e7612017-02-20 10:07:09 -0800262
Zack Williams045b63d2019-01-22 16:30:57 -0700263 _help = "enable verbose logging"
264 parser.add_argument("-v", "--verbose", dest="verbose", action="count", help=_help)
Scott Bakere72e7612017-02-20 10:07:09 -0800265
266 args = parser.parse_args()
267
268 return args
269
Sapan Bhatia51885532017-08-29 01:55:39 -0400270
Scott Bakere72e7612017-02-20 10:07:09 -0800271def coreclient_reconnect(client, reconnect_callback, *args, **kwargs):
272 global coreapi
273
274 coreapi = coreclient.xos_orm
275
276 if reconnect_callback:
277 reconnect_callback(*args, **kwargs)
278
279 reactor.stop()
280
Zack Williams045b63d2019-01-22 16:30:57 -0700281
Scott Bakere72e7612017-02-20 10:07:09 -0800282def start_api(reconnect_callback, *args, **kwargs):
283 global coreclient
284
285 if kwargs.get("username", None):
286 coreclient = SecureClient(*args, **kwargs)
287 else:
288 coreclient = InsecureClient(*args, **kwargs)
289
Zack Williams045b63d2019-01-22 16:30:57 -0700290 coreclient.set_reconnect_callback(
291 functools.partial(coreclient_reconnect, coreclient, reconnect_callback)
292 )
Scott Bakere72e7612017-02-20 10:07:09 -0800293 coreclient.start()
294
295 reactor.run()
296
Zack Williams045b63d2019-01-22 16:30:57 -0700297
Scott Bakere72e7612017-02-20 10:07:09 -0800298def start_api_parseargs(reconnect_callback):
Scott Baker95f7d952017-03-09 10:04:26 -0800299 """ This function is an entrypoint for tests and other simple programs to
300 setup the API and get a callback when the API is ready.
301 """
302
Scott Bakere72e7612017-02-20 10:07:09 -0800303 args = parse_args()
304
305 if args.username:
Zack Williams045b63d2019-01-22 16:30:57 -0700306 start_api(
307 reconnect_callback,
308 endpoint=args.grpc_secure_endpoint,
309 username=args.username,
310 password=args.password,
311 )
Scott Bakere72e7612017-02-20 10:07:09 -0800312 else:
313 start_api(reconnect_callback, endpoint=args.grpc_insecure_endpoint)
314
315
Scott Bakere72e7612017-02-20 10:07:09 -0800316# -----------------------------------------------------------------------------
Scott Baker96b995a2017-02-15 16:21:12 -0800317# Self test
318# -----------------------------------------------------------------------------
319
Zack Williams045b63d2019-01-22 16:30:57 -0700320
Scott Baker96b995a2017-02-15 16:21:12 -0800321def insecure_callback(client):
Zack Williams045b63d2019-01-22 16:30:57 -0700322 print("insecure self_test start")
323 print(client.xos_orm.User.objects.all())
324 print("insecure self_test done")
Scott Baker96b995a2017-02-15 16:21:12 -0800325
326 # now start the next test
327 client.stop()
328 reactor.callLater(0, start_secure_test)
329
Zack Williams045b63d2019-01-22 16:30:57 -0700330
Scott Baker96b995a2017-02-15 16:21:12 -0800331def start_insecure_test():
Matteo Scandoloe3d2f262018-06-05 17:45:39 -0700332 client = InsecureClient(endpoint="xos-core:50055")
Scott Baker96b995a2017-02-15 16:21:12 -0800333 client.set_reconnect_callback(functools.partial(insecure_callback, client))
334 client.start()
335
Zack Williams045b63d2019-01-22 16:30:57 -0700336
Scott Baker96b995a2017-02-15 16:21:12 -0800337def secure_callback(client):
Zack Williams045b63d2019-01-22 16:30:57 -0700338 print("secure self_test start")
339 print(client.xos_orm.User.objects.all())
340 print("secure self_test done")
Scott Baker96b995a2017-02-15 16:21:12 -0800341 reactor.stop()
342
Zack Williams045b63d2019-01-22 16:30:57 -0700343
Scott Baker96b995a2017-02-15 16:21:12 -0800344def start_secure_test():
Zack Williams045b63d2019-01-22 16:30:57 -0700345 client = SecureClient(
346 endpoint="xos-core:50051", username="admin@opencord.org", password="letmein"
347 )
Scott Baker96b995a2017-02-15 16:21:12 -0800348 client.set_reconnect_callback(functools.partial(secure_callback, client))
349 client.start()
350
Zack Williams045b63d2019-01-22 16:30:57 -0700351
Scott Baker96b995a2017-02-15 16:21:12 -0800352def main():
353 reactor.callLater(0, start_insecure_test)
354
355 reactor.run()
356
Scott Baker96b995a2017-02-15 16:21:12 -0800357
Zack Williams045b63d2019-01-22 16:30:57 -0700358if __name__ == "__main__":
359 main()