blob: 477b2b3bfbff1498aa6cbfb1464095a0d5d4758c [file] [log] [blame]
Zsolt Haraszti656ecc62016-12-28 15:08:23 -08001#!/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#
17
18"""
19PON Simulator process, able to move packets across NNI and UNIs, as well
20as take MGMT calls via gRPC.
21It can only work on Linux.
22"""
23import argparse
24import os
25
26import yaml
27from twisted.internet.defer import inlineCallbacks
28
29from common.structlog_setup import setup_logging
30from grpc_server import GrpcServer
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080031from realio import RealIo
Shad Ansarida0f3a42017-07-19 09:51:06 -070032from ponsim import PonSim
Nikolay Titov89004ec2017-06-19 18:22:42 -040033from ponsim import XPonSim
34
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080035defs = dict(
36 config=os.environ.get('CONFIG', './ponsim.yml'),
37 grpc_port=int(os.environ.get('GRPC_PORT', 50060)),
38 name=os.environ.get('NAME', 'pon1'),
Shad Ansarida0f3a42017-07-19 09:51:06 -070039 onus=int(os.environ.get("ONUS", 1)),
40 device_type='ponsim'
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080041)
42
43
44def load_config(args):
45 path = args.config
46 if path.startswith('.'):
47 dir = os.path.dirname(os.path.abspath(__file__))
48 path = os.path.join(dir, path)
49 path = os.path.abspath(path)
50 with open(path) as fd:
51 config = yaml.load(fd)
52 return config
53
54
55banner = r'''
56 ____ __ __ _ ____ __ _ _
57( _ \ / \ ( ( \/ ___)( )( \/ )
58 ) __/( O )/ /\___ \ )( / \/ \
59(__) \__/ \_)__)(____/(__)\_)(_/
60'''
61
62def print_banner(log):
63 for line in banner.strip('\n').splitlines():
64 log.info(line)
65 log.info('(to stop: press Ctrl-C)')
66
67
68def parse_args():
69
70 parser = argparse.ArgumentParser()
71
khenaidoo079a7762017-10-26 21:42:05 -040072 _help = ('Path to ponsim.yml config file (default: %s). '
73 'If relative, it is relative to main.py of ponsim.'
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080074 % defs['config'])
75 parser.add_argument('-c', '--config',
76 dest='config',
77 action='store',
78 default=defs['config'],
79 help=_help)
80
81 _help = ('port number of the GRPC service exposed by voltha (default: %s)'
82 % defs['grpc_port'])
83 parser.add_argument('-g', '--grpc-port',
84 dest='grpc_port',
85 action='store',
86 default=defs['grpc_port'],
87 help=_help)
88
89 _help = ('number of ONUs to simulate (default: %d)' % defs['onus'])
90 parser.add_argument('-o', '--onus',
91 dest='onus',
92 action='store',
93 type=int,
94 default=defs['onus'],
95 help=_help)
96
97 _help = ('name of the PON natework used as a prefix for all network'
98 ' resource created on behalf of the PON (default: %s)' %
99 defs['name'])
100 parser.add_argument('-N', '--name',
101 dest='name',
102 action='store',
103 default=defs['name'],
104 help=_help)
105
106 _help = "suppress debug and info logs"
107 parser.add_argument('-q', '--quiet',
108 dest='quiet',
109 action='count',
110 help=_help)
111
112 _help = 'enable verbose logging'
113 parser.add_argument('-v', '--verbose',
114 dest='verbose',
115 action='count',
116 help=_help)
117
Stephane Barbarie4475a252017-03-31 13:49:20 -0400118 _help = 'enable generation of simulated alarms'
119 parser.add_argument('-a', '--alarm-simulation',
120 dest='alarm_simulation',
121 action='store_true',
122 default=False,
123 help=_help)
124
125 _help = 'frequency of simulated alarms (in seconds)'
126 parser.add_argument('-f', '--alarm-frequency',
127 dest='alarm_frequency',
128 action='store',
129 type=int,
khenaidoo032d3302017-06-09 14:50:04 -0400130 metavar="[5-300]",
131 choices=range(5,301),
Stephane Barbarie4475a252017-03-31 13:49:20 -0400132 default=60,
133 help=_help)
134
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800135 _help = 'omit startup banner log lines'
136 parser.add_argument('-n', '--no-banner',
137 dest='no_banner',
138 action='store_true',
139 default=False,
140 help=_help)
141
Shad Ansarida0f3a42017-07-19 09:51:06 -0700142 _help = ('device type - ponsim or bal'
143 ' (default: %s)' % defs['device_type'])
144 parser.add_argument('-d', '--device_type',
145 dest='device_type',
146 action='store',
147 default=defs['device_type'],
148 help=_help)
149
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800150 args = parser.parse_args()
151
152 return args
153
154
155class Main(object):
156
157 def __init__(self):
158
159 self.args = args = parse_args()
160 self.config = load_config(args)
161
162 verbosity_adjust = (args.verbose or 0) - (args.quiet or 0)
163 self.log = setup_logging(self.config.get('logging', {}),
164 args.name,
165 verbosity_adjust=verbosity_adjust)
166
167 # components
168 self.io = None
169 self.ponsim = None
Nikolay Titov89004ec2017-06-19 18:22:42 -0400170 self.x_pon_sim = None
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800171 self.grpc_server = None
Shad Ansari2d6e4832017-07-21 16:24:14 -0700172 self.grpc_services = None
173 self.device_type = args.device_type
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800174
Stephane Barbarie4475a252017-03-31 13:49:20 -0400175 self.alarm_config = dict()
176 self.alarm_config['simulation'] = self.args.alarm_simulation
177 self.alarm_config['frequency'] = self.args.alarm_frequency
178
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800179 if not args.no_banner:
180 print_banner(self.log)
181
Shad Ansari2d6e4832017-07-21 16:24:14 -0700182 self.startup_components()
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800183
184 def start(self):
185 self.start_reactor() # will not return except Keyboard interrupt
186
187 @inlineCallbacks
Shad Ansari2d6e4832017-07-21 16:24:14 -0700188 def startup_components(self):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800189 try:
190 self.log.info('starting-internal-components')
191
192 iface_map = self.setup_networking_assets(self.args.name,
193 self.args.onus)
194 self.io = yield RealIo(iface_map).start()
Stephane Barbarie4475a252017-03-31 13:49:20 -0400195 self.ponsim = PonSim(self.args.onus, self.io.egress, self.alarm_config)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800196 self.io.register_ponsim(self.ponsim)
197
Nikolay Titov89004ec2017-06-19 18:22:42 -0400198 self.x_pon_sim = XPonSim()
199
Shad Ansari2d6e4832017-07-21 16:24:14 -0700200 self.grpc_server = GrpcServer(self.args.grpc_port,
201 self.ponsim,
202 self.x_pon_sim,
203 self.device_type)
204 yield self.grpc_server.start()
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800205
206 self.log.info('started-internal-services')
207
208 except Exception, e:
209 self.log.exception('startup-failed', e=e)
210
211 @inlineCallbacks
212 def shutdown_components(self):
213 """Execute before the reactor is shut down"""
214 self.log.info('exiting-on-keyboard-interrupt')
Rouzbahan Rashidi-Tabrizi66aa41d2017-02-24 09:30:30 -0500215 try:
216 if self.io is not None:
217 yield self.io.stop()
218 self.teardown_networking_assets(self.args.name, self.args.onus)
219 if self.grpc_server is not None:
220 yield self.grpc_server.stop()
221 except Exception, e:
222 self.log.exception('shutdown-failed', e=e)
Rouzbahan Rashidi-Tabrizi4f42f632017-02-23 17:13:31 -0500223
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800224 def start_reactor(self):
225 from twisted.internet import reactor
226 reactor.callWhenRunning(
227 lambda: self.log.info('twisted-reactor-started'))
228 reactor.addSystemEventTrigger('before', 'shutdown',
229 self.shutdown_components)
230 reactor.run()
231
232 def setup_networking_assets(self, prefix, n_unis):
233 # setup veth pairs for NNI and each UNI, using prefix and port numbers
234 port_map = dict()
235 for portnum in [0] + range(128, 128 + n_unis):
236 external_name = '%s_%d' % (prefix, portnum)
237 internal_name = external_name + 'sim'
238 os.system('sudo ip link add dev {} type veth peer name {}'.format(
239 external_name, internal_name
240 ))
241 os.system('sudo ip link set {} up'.format(external_name))
242 os.system('sudo ip link set {} up'.format(internal_name))
243 if portnum == 0:
244 os.system('sudo brctl addif ponmgmt {}'.format(external_name))
245 port_map[portnum] = internal_name
246 return port_map
247
248 def teardown_networking_assets(self, prefix, n_unis):
249 # undo all the networking stuff
250 for portnum in [0] + range(128, 128 + n_unis):
251 external_name = '%s_%d' % (prefix, portnum)
252 os.system('sudo ip link del {}'.format(external_name))
253
254
255if __name__ == '__main__':
256 Main().start()