blob: 6f096a84584661332df9f0b3c1d8aacfdacb8b27 [file] [log] [blame]
Khen Nursimulu3869d8d2016-11-28 20:44:28 -05001#!/usr/bin/env python
2#
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -05003# Copyright 2017 the original author or authors.
Khen Nursimulu3869d8d2016-11-28 20:44:28 -05004#
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()),
Richard Jankowskibbe1e092018-01-18 13:48:20 -050039 netconf_port=os.environ.get('NETCONF_PORT', '1830'),
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050040 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'),
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050049 instance_id=os.environ.get('INSTANCE_ID', os.environ.get('HOSTNAME', '1')),
50 internal_host_address=os.environ.get('INTERNAL_HOST_ADDRESS',
51 get_my_primary_local_ipv4()),
52 work_dir=os.environ.get('WORK_DIR', '/tmp/netconf')
53)
54
55
56def parse_args():
57 parser = argparse.ArgumentParser()
58
59 _help = ('Path to netconf.yml config file (default: %s). '
60 'If relative, it is relative to main.py of ofagent.'
61 % defs['config'])
62 parser.add_argument('-c', '--config',
63 dest='config',
64 action='store',
65 default=defs['config'],
66 help=_help)
67
68 _help = '<hostname>:<port> to consul agent (default: %s)' % defs['consul']
69 parser.add_argument(
70 '-C', '--consul', dest='consul', action='store',
71 default=defs['consul'],
72 help=_help)
73
74 _help = ('<hostname> or <ip> at which netconf is reachable from '
75 'outside the cluster (default: %s)' % defs[
76 'external_host_address'])
77 parser.add_argument('-E', '--external-host-address',
78 dest='external_host_address',
79 action='store',
80 default=defs['external_host_address'],
81 help=_help)
82
83 _help = ('<port> of netconf server (default: %s). (If not '
84 'specified (None), the port from the config file is used'
85 % defs['netconf_port'])
86 parser.add_argument('-N', '--netconf_port',
87 dest='netconf_port',
88 action='store',
89 default=defs['netconf_port'],
90 help=_help)
91
92 _help = (
93 '<server private key file name> used by the netconf server. (If not '
94 'specified (None), the file name from the config file is used (default: %s)'
95 % defs['server_private_key_file'])
96 parser.add_argument('-S', '--server_private_key_file',
97 dest='server_private_key_file',
98 action='store',
99 default=defs['server_private_key_file'],
100 help=_help)
101
102 _help = ('<server public key file name> used by the netconf server. (If '
103 'not specified (None), the file name from the config file is '
104 'used (default: %s) '
105 % defs['server_public_key_file'])
106 parser.add_argument('-P', '--server_public_key_file',
107 dest='server_public_key_file',
108 action='store',
109 default=defs['server_public_key_file'],
110 help=_help)
111
112 _help = ('<client public key file name> used by the netconf server. (If '
113 'not specified (None), the file name from the config file is '
114 'used(default: %s) '
115 % defs['client_public_keys_file'])
116 parser.add_argument('-X', '--client_public_keys_file',
117 dest='client_public_keys_file',
118 action='store',
119 default=defs['client_public_keys_file'],
120 help=_help)
121
122 _help = ('<client password file name> used by the netconf server. (If '
123 'not specified (None), the file name from the config file is '
124 'used (default: %s) '
125 % defs['client_passwords_file'])
126 parser.add_argument('-U', '--client_passwords_file',
127 dest='client_passwords_file',
128 action='store',
129 default=defs['client_passwords_file'],
130 help=_help)
131
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500132 _help = ('gRPC end-point to connect to. It can either be a direct'
133 'definition in the form of <hostname>:<port>, or it can be an'
134 'indirect definition in the form of @<service-name> where'
135 '<service-name> is the name of the grpc service as registered'
136 'in consul (example: @voltha-grpc). (default: %s'
137 % defs['grpc_endpoint'])
138 parser.add_argument('-G', '--grpc-endpoint',
139 dest='grpc_endpoint',
140 action='store',
141 default=defs['grpc_endpoint'],
142 help=_help)
143
144 _help = ('<hostname> or <ip> at which netconf server is reachable from '
145 'inside the cluster (default: %s)' % defs[
146 'internal_host_address'])
147 parser.add_argument('-H', '--internal-host-address',
148 dest='internal_host_address',
149 action='store',
150 default=defs['internal_host_address'],
151 help=_help)
152
153 _help = ('unique string id of this netconf server instance (default: %s)'
154 % defs['instance_id'])
155 parser.add_argument('-i', '--instance-id',
156 dest='instance_id',
157 action='store',
158 default=defs['instance_id'],
159 help=_help)
160
161 _help = 'omit startup banner log lines'
162 parser.add_argument('-n', '--no-banner',
163 dest='no_banner',
164 action='store_true',
165 default=False,
166 help=_help)
167
168 _help = "suppress debug and info logs"
169 parser.add_argument('-q', '--quiet',
170 dest='quiet',
171 action='count',
172 help=_help)
173
174 _help = 'enable verbose logging'
175 parser.add_argument('-v', '--verbose',
176 dest='verbose',
177 action='count',
178 help=_help)
179
180 _help = ('work dir to compile and assemble generated files (default=%s)'
181 % defs['work_dir'])
182 parser.add_argument('-w', '--work-dir',
183 dest='work_dir',
184 action='store',
185 default=defs['work_dir'],
186 help=_help)
187
188 _help = ('use docker container name as netconf server instance id'
189 ' (overrides -i/--instance-id option)')
190 parser.add_argument('--instance-id-is-container-name',
191 dest='instance_id_is_container_name',
192 action='store_true',
193 default=False,
194 help=_help)
195
196 args = parser.parse_args()
197
198 # post-processing
199
200 if args.instance_id_is_container_name:
201 args.instance_id = get_my_containers_name()
202
203 return args
204
205
206def load_config(args):
207 path = args.config
208 if path.startswith('.'):
209 dir = os.path.dirname(os.path.abspath(__file__))
210 path = os.path.join(dir, path)
211 path = os.path.abspath(path)
212 with open(path) as fd:
213 config = yaml.load(fd)
214 return config
215
216
217banner = r'''
218 _ _ _ __ ____
219| \ | | ___| |_ ___ ___ _ __ / _| / ___| ___ _ ____ _____ _ __
220| \| |/ _ \ __/ __/ _ \| '_ \| |_ \___ \ / _ \ '__\ \ / / _ \ '__|
221| |\ | __/ || (_| (_) | | | | _| ___) | __/ | \ V / __/ |
222|_| \_|\___|\__\___\___/|_| |_|_| |____/ \___|_| \_/ \___|_|
223'''
224
225
226def print_banner(log):
227 for line in banner.strip('\n').splitlines():
228 log.info(line)
229 log.info('(to stop: press Ctrl-C)')
230
231
232class Main(object):
233 def __init__(self):
234
235 self.args = args = parse_args()
236 self.config = load_config(args)
237
238 verbosity_adjust = (args.verbose or 0) - (args.quiet or 0)
239 self.log = setup_logging(self.config.get('logging', {}),
240 args.instance_id,
khenaidoo50b286d2018-03-02 17:44:30 -0500241 verbosity_adjust=verbosity_adjust)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500242
243 # components
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500244 self.nc_server = None
245 self.grpc_client = None # single, shared gRPC client to Voltha
246
247 self.netconf_server_started = False
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500248
249 self.exiting = False
250
251 if not args.no_banner:
252 print_banner(self.log)
253
254 self.startup_components()
255
256 def start(self):
257 self.start_reactor() # will not return except Keyboard interrupt
258
259 @inlineCallbacks
260 def startup_components(self):
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500261 self.log.info('starting')
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500262 args = self.args
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500263
264 self.grpc_client = yield \
265 GrpcClient(args.consul, args.work_dir, args.grpc_endpoint)
266
267 self.nc_server = yield \
Richard Jankowskibbe1e092018-01-18 13:48:20 -0500268 NCServer(int(args.netconf_port),
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500269 args.server_private_key_file,
270 args.server_public_key_file,
271 args.client_public_keys_file,
272 args.client_passwords_file,
273 self.grpc_client)
274
275 # set on start callback
276 self.grpc_client.set_on_start_callback(self._start_netconf_server)
277
278 # set the callback if there is a reconnect with voltha.
279 self.grpc_client.set_reconnect_callback(self.nc_server.reload_capabilities)
280
281 # start grpc client
282 self.grpc_client.start()
283
284 self.log.info('started')
285
286 @inlineCallbacks
287 def _start_netconf_server(self):
288 if not self.netconf_server_started:
289 self.log.info('starting')
290 yield self.nc_server.start()
291 self.netconf_server_started = True
292 self.log.info('started')
293 else:
294 self.log.info('server-already-started')
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500295
296 @inlineCallbacks
297 def shutdown_components(self):
298 """Execute before the reactor is shut down"""
299 self.log.info('exiting-on-keyboard-interrupt')
300 self.exiting = True
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500301
302 if self.grpc_client is not None:
303 yield self.grpc_client.stop()
304
305 if self.nc_server is not None:
306 yield self.nc_server.stop()
307
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500308
309 def start_reactor(self):
310 reactor.callWhenRunning(
311 lambda: self.log.info('twisted-reactor-started'))
312
313 reactor.addSystemEventTrigger('before', 'shutdown',
314 self.shutdown_components)
315 reactor.run()
316
317
318if __name__ == '__main__':
319 Main().start()