blob: e049433eecabef012697dc6e2e12d1bb1a183dd3 [file] [log] [blame]
Khen Nursimulu3869d8d2016-11-28 20:44:28 -05001#!/usr/bin/env python
2#
3# Copyright 2016 the original author or authors.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17import argparse
18import os
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050019import sys
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050020import yaml
21from twisted.internet import reactor
22from twisted.internet.defer import inlineCallbacks
23
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050024base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
25sys.path.append(base_dir)
26sys.path.append(os.path.join(base_dir, '/netconf/protos/third_party'))
27
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050028from common.structlog_setup import setup_logging
29from common.utils.dockerhelpers import get_my_containers_name
30from common.utils.nethelpers import get_my_primary_local_ipv4
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050031from netconf.grpc_client.grpc_client import GrpcClient
32from netconf.nc_server import NCServer
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050033
34defs = dict(
35 config=os.environ.get('CONFIG', './netconf.yml'),
36 consul=os.environ.get('CONSUL', 'localhost:8500'),
37 external_host_address=os.environ.get('EXTERNAL_HOST_ADDRESS',
38 get_my_primary_local_ipv4()),
39 netconf_port=os.environ.get('NETCONF_PORT', 1830),
40 server_private_key_file=os.environ.get('SERVER_PRIVATE_KEY_FILE',
41 'server.key'),
42 server_public_key_file=os.environ.get('SERVER_PRIVATE_KEY_FILE',
43 'server.key.pub'),
44 client_public_keys_file=os.environ.get('CLIENT_PUBLIC_KEYS_FILE',
45 'client_keys'),
46 client_passwords_file=os.environ.get('CLIENT_PASSWORD_FILE',
47 'client_passwords'),
48 grpc_endpoint=os.environ.get('GRPC_ENDPOINT', 'localhost:50055'),
49 fluentd=os.environ.get('FLUENTD', None),
50 instance_id=os.environ.get('INSTANCE_ID', os.environ.get('HOSTNAME', '1')),
51 internal_host_address=os.environ.get('INTERNAL_HOST_ADDRESS',
52 get_my_primary_local_ipv4()),
53 work_dir=os.environ.get('WORK_DIR', '/tmp/netconf')
54)
55
56
57def parse_args():
58 parser = argparse.ArgumentParser()
59
60 _help = ('Path to netconf.yml config file (default: %s). '
61 'If relative, it is relative to main.py of ofagent.'
62 % defs['config'])
63 parser.add_argument('-c', '--config',
64 dest='config',
65 action='store',
66 default=defs['config'],
67 help=_help)
68
69 _help = '<hostname>:<port> to consul agent (default: %s)' % defs['consul']
70 parser.add_argument(
71 '-C', '--consul', dest='consul', action='store',
72 default=defs['consul'],
73 help=_help)
74
75 _help = ('<hostname> or <ip> at which netconf is reachable from '
76 'outside the cluster (default: %s)' % defs[
77 'external_host_address'])
78 parser.add_argument('-E', '--external-host-address',
79 dest='external_host_address',
80 action='store',
81 default=defs['external_host_address'],
82 help=_help)
83
84 _help = ('<port> of netconf server (default: %s). (If not '
85 'specified (None), the port from the config file is used'
86 % defs['netconf_port'])
87 parser.add_argument('-N', '--netconf_port',
88 dest='netconf_port',
89 action='store',
90 default=defs['netconf_port'],
91 help=_help)
92
93 _help = (
94 '<server private key file name> used by the netconf server. (If not '
95 'specified (None), the file name from the config file is used (default: %s)'
96 % defs['server_private_key_file'])
97 parser.add_argument('-S', '--server_private_key_file',
98 dest='server_private_key_file',
99 action='store',
100 default=defs['server_private_key_file'],
101 help=_help)
102
103 _help = ('<server public key file name> used by the netconf server. (If '
104 'not specified (None), the file name from the config file is '
105 'used (default: %s) '
106 % defs['server_public_key_file'])
107 parser.add_argument('-P', '--server_public_key_file',
108 dest='server_public_key_file',
109 action='store',
110 default=defs['server_public_key_file'],
111 help=_help)
112
113 _help = ('<client public key file name> used by the netconf server. (If '
114 'not specified (None), the file name from the config file is '
115 'used(default: %s) '
116 % defs['client_public_keys_file'])
117 parser.add_argument('-X', '--client_public_keys_file',
118 dest='client_public_keys_file',
119 action='store',
120 default=defs['client_public_keys_file'],
121 help=_help)
122
123 _help = ('<client password file name> used by the netconf server. (If '
124 'not specified (None), the file name from the config file is '
125 'used (default: %s) '
126 % defs['client_passwords_file'])
127 parser.add_argument('-U', '--client_passwords_file',
128 dest='client_passwords_file',
129 action='store',
130 default=defs['client_passwords_file'],
131 help=_help)
132
133 _help = ('<hostname>:<port> to fluentd server (default: %s). (If not '
134 'specified (None), the address from the config file is used'
135 % defs['fluentd'])
136 parser.add_argument('-F', '--fluentd',
137 dest='fluentd',
138 action='store',
139 default=defs['fluentd'],
140 help=_help)
141
142 _help = ('gRPC end-point to connect to. It can either be a direct'
143 'definition in the form of <hostname>:<port>, or it can be an'
144 'indirect definition in the form of @<service-name> where'
145 '<service-name> is the name of the grpc service as registered'
146 'in consul (example: @voltha-grpc). (default: %s'
147 % defs['grpc_endpoint'])
148 parser.add_argument('-G', '--grpc-endpoint',
149 dest='grpc_endpoint',
150 action='store',
151 default=defs['grpc_endpoint'],
152 help=_help)
153
154 _help = ('<hostname> or <ip> at which netconf server is reachable from '
155 'inside the cluster (default: %s)' % defs[
156 'internal_host_address'])
157 parser.add_argument('-H', '--internal-host-address',
158 dest='internal_host_address',
159 action='store',
160 default=defs['internal_host_address'],
161 help=_help)
162
163 _help = ('unique string id of this netconf server instance (default: %s)'
164 % defs['instance_id'])
165 parser.add_argument('-i', '--instance-id',
166 dest='instance_id',
167 action='store',
168 default=defs['instance_id'],
169 help=_help)
170
171 _help = 'omit startup banner log lines'
172 parser.add_argument('-n', '--no-banner',
173 dest='no_banner',
174 action='store_true',
175 default=False,
176 help=_help)
177
178 _help = "suppress debug and info logs"
179 parser.add_argument('-q', '--quiet',
180 dest='quiet',
181 action='count',
182 help=_help)
183
184 _help = 'enable verbose logging'
185 parser.add_argument('-v', '--verbose',
186 dest='verbose',
187 action='count',
188 help=_help)
189
190 _help = ('work dir to compile and assemble generated files (default=%s)'
191 % defs['work_dir'])
192 parser.add_argument('-w', '--work-dir',
193 dest='work_dir',
194 action='store',
195 default=defs['work_dir'],
196 help=_help)
197
198 _help = ('use docker container name as netconf server instance id'
199 ' (overrides -i/--instance-id option)')
200 parser.add_argument('--instance-id-is-container-name',
201 dest='instance_id_is_container_name',
202 action='store_true',
203 default=False,
204 help=_help)
205
206 args = parser.parse_args()
207
208 # post-processing
209
210 if args.instance_id_is_container_name:
211 args.instance_id = get_my_containers_name()
212
213 return args
214
215
216def load_config(args):
217 path = args.config
218 if path.startswith('.'):
219 dir = os.path.dirname(os.path.abspath(__file__))
220 path = os.path.join(dir, path)
221 path = os.path.abspath(path)
222 with open(path) as fd:
223 config = yaml.load(fd)
224 return config
225
226
227banner = r'''
228 _ _ _ __ ____
229| \ | | ___| |_ ___ ___ _ __ / _| / ___| ___ _ ____ _____ _ __
230| \| |/ _ \ __/ __/ _ \| '_ \| |_ \___ \ / _ \ '__\ \ / / _ \ '__|
231| |\ | __/ || (_| (_) | | | | _| ___) | __/ | \ V / __/ |
232|_| \_|\___|\__\___\___/|_| |_|_| |____/ \___|_| \_/ \___|_|
233'''
234
235
236def print_banner(log):
237 for line in banner.strip('\n').splitlines():
238 log.info(line)
239 log.info('(to stop: press Ctrl-C)')
240
241
242class Main(object):
243 def __init__(self):
244
245 self.args = args = parse_args()
246 self.config = load_config(args)
247
248 verbosity_adjust = (args.verbose or 0) - (args.quiet or 0)
249 self.log = setup_logging(self.config.get('logging', {}),
250 args.instance_id,
251 verbosity_adjust=verbosity_adjust,
252 fluentd=args.fluentd)
253
254 # components
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500255 self.nc_server = None
256 self.grpc_client = None # single, shared gRPC client to Voltha
257
258 self.netconf_server_started = False
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500259
260 self.exiting = False
261
262 if not args.no_banner:
263 print_banner(self.log)
264
265 self.startup_components()
266
267 def start(self):
268 self.start_reactor() # will not return except Keyboard interrupt
269
270 @inlineCallbacks
271 def startup_components(self):
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500272 self.log.info('starting')
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500273 args = self.args
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500274
275 self.grpc_client = yield \
276 GrpcClient(args.consul, args.work_dir, args.grpc_endpoint)
277
278 self.nc_server = yield \
279 NCServer(args.netconf_port,
280 args.server_private_key_file,
281 args.server_public_key_file,
282 args.client_public_keys_file,
283 args.client_passwords_file,
284 self.grpc_client)
285
286 # set on start callback
287 self.grpc_client.set_on_start_callback(self._start_netconf_server)
288
289 # set the callback if there is a reconnect with voltha.
290 self.grpc_client.set_reconnect_callback(self.nc_server.reload_capabilities)
291
292 # start grpc client
293 self.grpc_client.start()
294
295 self.log.info('started')
296
297 @inlineCallbacks
298 def _start_netconf_server(self):
299 if not self.netconf_server_started:
300 self.log.info('starting')
301 yield self.nc_server.start()
302 self.netconf_server_started = True
303 self.log.info('started')
304 else:
305 self.log.info('server-already-started')
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500306
307 @inlineCallbacks
308 def shutdown_components(self):
309 """Execute before the reactor is shut down"""
310 self.log.info('exiting-on-keyboard-interrupt')
311 self.exiting = True
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500312
313 if self.grpc_client is not None:
314 yield self.grpc_client.stop()
315
316 if self.nc_server is not None:
317 yield self.nc_server.stop()
318
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500319
320 def start_reactor(self):
321 reactor.callWhenRunning(
322 lambda: self.log.info('twisted-reactor-started'))
323
324 reactor.addSystemEventTrigger('before', 'shutdown',
325 self.shutdown_components)
326 reactor.run()
327
328
329if __name__ == '__main__':
330 Main().start()