blob: 3140afd15d0162b05ec824419556e6c0be27afa3 [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
31from ponsim import PonSim
32from realio import RealIo
33
Nikolay Titov89004ec2017-06-19 18:22:42 -040034from ponsim import XPonSim
35
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080036defs = dict(
37 config=os.environ.get('CONFIG', './ponsim.yml'),
38 grpc_port=int(os.environ.get('GRPC_PORT', 50060)),
39 name=os.environ.get('NAME', 'pon1'),
40 onus=int(os.environ.get("ONUS", 1))
41)
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
72 _help = ('Path to chameleon.yml config file (default: %s). '
73 'If relative, it is relative to main.py of chameleon.'
74 % 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
142 args = parser.parse_args()
143
144 return args
145
146
147class Main(object):
148
149 def __init__(self):
150
151 self.args = args = parse_args()
152 self.config = load_config(args)
153
154 verbosity_adjust = (args.verbose or 0) - (args.quiet or 0)
155 self.log = setup_logging(self.config.get('logging', {}),
156 args.name,
157 verbosity_adjust=verbosity_adjust)
158
159 # components
160 self.io = None
161 self.ponsim = None
Nikolay Titov89004ec2017-06-19 18:22:42 -0400162 self.x_pon_sim = None
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800163 self.grpc_server = None
164
Stephane Barbarie4475a252017-03-31 13:49:20 -0400165 self.alarm_config = dict()
166 self.alarm_config['simulation'] = self.args.alarm_simulation
167 self.alarm_config['frequency'] = self.args.alarm_frequency
168
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800169 if not args.no_banner:
170 print_banner(self.log)
171
172 self.startup_components()
173
174 def start(self):
175 self.start_reactor() # will not return except Keyboard interrupt
176
177 @inlineCallbacks
178 def startup_components(self):
179 try:
180 self.log.info('starting-internal-components')
181
182 iface_map = self.setup_networking_assets(self.args.name,
183 self.args.onus)
184 self.io = yield RealIo(iface_map).start()
Stephane Barbarie4475a252017-03-31 13:49:20 -0400185 self.ponsim = PonSim(self.args.onus, self.io.egress, self.alarm_config)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800186 self.io.register_ponsim(self.ponsim)
187
Nikolay Titov89004ec2017-06-19 18:22:42 -0400188 self.x_pon_sim = XPonSim()
189
190 self.grpc_server = GrpcServer(self.args.grpc_port, self.ponsim, self.x_pon_sim)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800191 yield self.grpc_server.start()
192
193 self.log.info('started-internal-services')
194
195 except Exception, e:
196 self.log.exception('startup-failed', e=e)
197
198 @inlineCallbacks
199 def shutdown_components(self):
200 """Execute before the reactor is shut down"""
201 self.log.info('exiting-on-keyboard-interrupt')
Rouzbahan Rashidi-Tabrizi66aa41d2017-02-24 09:30:30 -0500202 try:
203 if self.io is not None:
204 yield self.io.stop()
205 self.teardown_networking_assets(self.args.name, self.args.onus)
206 if self.grpc_server is not None:
207 yield self.grpc_server.stop()
208 except Exception, e:
209 self.log.exception('shutdown-failed', e=e)
Rouzbahan Rashidi-Tabrizi4f42f632017-02-23 17:13:31 -0500210
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800211 def start_reactor(self):
212 from twisted.internet import reactor
213 reactor.callWhenRunning(
214 lambda: self.log.info('twisted-reactor-started'))
215 reactor.addSystemEventTrigger('before', 'shutdown',
216 self.shutdown_components)
217 reactor.run()
218
219 def setup_networking_assets(self, prefix, n_unis):
220 # setup veth pairs for NNI and each UNI, using prefix and port numbers
221 port_map = dict()
222 for portnum in [0] + range(128, 128 + n_unis):
223 external_name = '%s_%d' % (prefix, portnum)
224 internal_name = external_name + 'sim'
225 os.system('sudo ip link add dev {} type veth peer name {}'.format(
226 external_name, internal_name
227 ))
228 os.system('sudo ip link set {} up'.format(external_name))
229 os.system('sudo ip link set {} up'.format(internal_name))
230 if portnum == 0:
231 os.system('sudo brctl addif ponmgmt {}'.format(external_name))
232 port_map[portnum] = internal_name
233 return port_map
234
235 def teardown_networking_assets(self, prefix, n_unis):
236 # undo all the networking stuff
237 for portnum in [0] + range(128, 128 + n_unis):
238 external_name = '%s_%d' % (prefix, portnum)
239 os.system('sudo ip link del {}'.format(external_name))
240
241
242if __name__ == '__main__':
243 Main().start()