blob: 06edfe47b426a131057c1b8c81c7ad4a4467b1f3 [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
19
20import yaml
21from twisted.internet import reactor
22from twisted.internet.defer import inlineCallbacks
23
24from common.structlog_setup import setup_logging
25from common.utils.dockerhelpers import get_my_containers_name
26from common.utils.nethelpers import get_my_primary_local_ipv4
27from connection_mgr import ConnectionManager
28
29defs = dict(
30 config=os.environ.get('CONFIG', './netconf.yml'),
31 consul=os.environ.get('CONSUL', 'localhost:8500'),
32 external_host_address=os.environ.get('EXTERNAL_HOST_ADDRESS',
33 get_my_primary_local_ipv4()),
34 netconf_port=os.environ.get('NETCONF_PORT', 1830),
35 server_private_key_file=os.environ.get('SERVER_PRIVATE_KEY_FILE',
36 'server.key'),
37 server_public_key_file=os.environ.get('SERVER_PRIVATE_KEY_FILE',
38 'server.key.pub'),
39 client_public_keys_file=os.environ.get('CLIENT_PUBLIC_KEYS_FILE',
40 'client_keys'),
41 client_passwords_file=os.environ.get('CLIENT_PASSWORD_FILE',
42 'client_passwords'),
43 grpc_endpoint=os.environ.get('GRPC_ENDPOINT', 'localhost:50055'),
44 fluentd=os.environ.get('FLUENTD', None),
45 instance_id=os.environ.get('INSTANCE_ID', os.environ.get('HOSTNAME', '1')),
46 internal_host_address=os.environ.get('INTERNAL_HOST_ADDRESS',
47 get_my_primary_local_ipv4()),
48 work_dir=os.environ.get('WORK_DIR', '/tmp/netconf')
49)
50
51
52def parse_args():
53 parser = argparse.ArgumentParser()
54
55 _help = ('Path to netconf.yml config file (default: %s). '
56 'If relative, it is relative to main.py of ofagent.'
57 % defs['config'])
58 parser.add_argument('-c', '--config',
59 dest='config',
60 action='store',
61 default=defs['config'],
62 help=_help)
63
64 _help = '<hostname>:<port> to consul agent (default: %s)' % defs['consul']
65 parser.add_argument(
66 '-C', '--consul', dest='consul', action='store',
67 default=defs['consul'],
68 help=_help)
69
70 _help = ('<hostname> or <ip> at which netconf is reachable from '
71 'outside the cluster (default: %s)' % defs[
72 'external_host_address'])
73 parser.add_argument('-E', '--external-host-address',
74 dest='external_host_address',
75 action='store',
76 default=defs['external_host_address'],
77 help=_help)
78
79 _help = ('<port> of netconf server (default: %s). (If not '
80 'specified (None), the port from the config file is used'
81 % defs['netconf_port'])
82 parser.add_argument('-N', '--netconf_port',
83 dest='netconf_port',
84 action='store',
85 default=defs['netconf_port'],
86 help=_help)
87
88 _help = (
89 '<server private key file name> used by the netconf server. (If not '
90 'specified (None), the file name from the config file is used (default: %s)'
91 % defs['server_private_key_file'])
92 parser.add_argument('-S', '--server_private_key_file',
93 dest='server_private_key_file',
94 action='store',
95 default=defs['server_private_key_file'],
96 help=_help)
97
98 _help = ('<server public key file name> used by the netconf server. (If '
99 'not specified (None), the file name from the config file is '
100 'used (default: %s) '
101 % defs['server_public_key_file'])
102 parser.add_argument('-P', '--server_public_key_file',
103 dest='server_public_key_file',
104 action='store',
105 default=defs['server_public_key_file'],
106 help=_help)
107
108 _help = ('<client public key file name> used by the netconf server. (If '
109 'not specified (None), the file name from the config file is '
110 'used(default: %s) '
111 % defs['client_public_keys_file'])
112 parser.add_argument('-X', '--client_public_keys_file',
113 dest='client_public_keys_file',
114 action='store',
115 default=defs['client_public_keys_file'],
116 help=_help)
117
118 _help = ('<client password file name> used by the netconf server. (If '
119 'not specified (None), the file name from the config file is '
120 'used (default: %s) '
121 % defs['client_passwords_file'])
122 parser.add_argument('-U', '--client_passwords_file',
123 dest='client_passwords_file',
124 action='store',
125 default=defs['client_passwords_file'],
126 help=_help)
127
128 _help = ('<hostname>:<port> to fluentd server (default: %s). (If not '
129 'specified (None), the address from the config file is used'
130 % defs['fluentd'])
131 parser.add_argument('-F', '--fluentd',
132 dest='fluentd',
133 action='store',
134 default=defs['fluentd'],
135 help=_help)
136
137 _help = ('gRPC end-point to connect to. It can either be a direct'
138 'definition in the form of <hostname>:<port>, or it can be an'
139 'indirect definition in the form of @<service-name> where'
140 '<service-name> is the name of the grpc service as registered'
141 'in consul (example: @voltha-grpc). (default: %s'
142 % defs['grpc_endpoint'])
143 parser.add_argument('-G', '--grpc-endpoint',
144 dest='grpc_endpoint',
145 action='store',
146 default=defs['grpc_endpoint'],
147 help=_help)
148
149 _help = ('<hostname> or <ip> at which netconf server is reachable from '
150 'inside the cluster (default: %s)' % defs[
151 'internal_host_address'])
152 parser.add_argument('-H', '--internal-host-address',
153 dest='internal_host_address',
154 action='store',
155 default=defs['internal_host_address'],
156 help=_help)
157
158 _help = ('unique string id of this netconf server instance (default: %s)'
159 % defs['instance_id'])
160 parser.add_argument('-i', '--instance-id',
161 dest='instance_id',
162 action='store',
163 default=defs['instance_id'],
164 help=_help)
165
166 _help = 'omit startup banner log lines'
167 parser.add_argument('-n', '--no-banner',
168 dest='no_banner',
169 action='store_true',
170 default=False,
171 help=_help)
172
173 _help = "suppress debug and info logs"
174 parser.add_argument('-q', '--quiet',
175 dest='quiet',
176 action='count',
177 help=_help)
178
179 _help = 'enable verbose logging'
180 parser.add_argument('-v', '--verbose',
181 dest='verbose',
182 action='count',
183 help=_help)
184
185 _help = ('work dir to compile and assemble generated files (default=%s)'
186 % defs['work_dir'])
187 parser.add_argument('-w', '--work-dir',
188 dest='work_dir',
189 action='store',
190 default=defs['work_dir'],
191 help=_help)
192
193 _help = ('use docker container name as netconf server instance id'
194 ' (overrides -i/--instance-id option)')
195 parser.add_argument('--instance-id-is-container-name',
196 dest='instance_id_is_container_name',
197 action='store_true',
198 default=False,
199 help=_help)
200
201 args = parser.parse_args()
202
203 # post-processing
204
205 if args.instance_id_is_container_name:
206 args.instance_id = get_my_containers_name()
207
208 return args
209
210
211def load_config(args):
212 path = args.config
213 if path.startswith('.'):
214 dir = os.path.dirname(os.path.abspath(__file__))
215 path = os.path.join(dir, path)
216 path = os.path.abspath(path)
217 with open(path) as fd:
218 config = yaml.load(fd)
219 return config
220
221
222banner = r'''
223 _ _ _ __ ____
224| \ | | ___| |_ ___ ___ _ __ / _| / ___| ___ _ ____ _____ _ __
225| \| |/ _ \ __/ __/ _ \| '_ \| |_ \___ \ / _ \ '__\ \ / / _ \ '__|
226| |\ | __/ || (_| (_) | | | | _| ___) | __/ | \ V / __/ |
227|_| \_|\___|\__\___\___/|_| |_|_| |____/ \___|_| \_/ \___|_|
228'''
229
230
231def print_banner(log):
232 for line in banner.strip('\n').splitlines():
233 log.info(line)
234 log.info('(to stop: press Ctrl-C)')
235
236
237class Main(object):
238 def __init__(self):
239
240 self.args = args = parse_args()
241 self.config = load_config(args)
242
243 verbosity_adjust = (args.verbose or 0) - (args.quiet or 0)
244 self.log = setup_logging(self.config.get('logging', {}),
245 args.instance_id,
246 verbosity_adjust=verbosity_adjust,
247 fluentd=args.fluentd)
248
249 # components
250 self.connection_manager = None
251
252 self.exiting = False
253
254 if not args.no_banner:
255 print_banner(self.log)
256
257 self.startup_components()
258
259 def start(self):
260 self.start_reactor() # will not return except Keyboard interrupt
261
262 @inlineCallbacks
263 def startup_components(self):
264 self.log.info('starting-netconf-server')
265 args = self.args
266 self.connection_manager = yield ConnectionManager(
267 args.consul,
268 args.grpc_endpoint,
269 args.netconf_port,
270 args.server_private_key_file,
271 args.server_public_key_file,
272 args.client_public_keys_file,
273 args.client_passwords_file).start()
274 self.log.info('started-netconf-server')
275
276 @inlineCallbacks
277 def shutdown_components(self):
278 """Execute before the reactor is shut down"""
279 self.log.info('exiting-on-keyboard-interrupt')
280 self.exiting = True
281 if self.connection_manager is not None:
282 yield self.connection_manager.stop()
283
284 def start_reactor(self):
285 reactor.callWhenRunning(
286 lambda: self.log.info('twisted-reactor-started'))
287
288 reactor.addSystemEventTrigger('before', 'shutdown',
289 self.shutdown_components)
290 reactor.run()
291
292
293if __name__ == '__main__':
294 Main().start()