blob: 63b47f6dc78e6859bb4f53ec42a43b03f35f7078 [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 voltha.protos.ponsim_pb2 import add_PonSimServicer_to_server
33from voltha.protos.ponsim_pb2 import add_XPonSimServicer_to_server
34from voltha.adapters.asfvolt16_olt.protos.bal_pb2 import add_BalServicer_to_server
35import ponsim_servicer
36import bal_servicer
37from ponsim import PonSim
Nikolay Titov89004ec2017-06-19 18:22:42 -040038from ponsim import XPonSim
39
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080040defs = dict(
41 config=os.environ.get('CONFIG', './ponsim.yml'),
42 grpc_port=int(os.environ.get('GRPC_PORT', 50060)),
43 name=os.environ.get('NAME', 'pon1'),
Shad Ansarida0f3a42017-07-19 09:51:06 -070044 onus=int(os.environ.get("ONUS", 1)),
45 device_type='ponsim'
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080046)
47
48
49def load_config(args):
50 path = args.config
51 if path.startswith('.'):
52 dir = os.path.dirname(os.path.abspath(__file__))
53 path = os.path.join(dir, path)
54 path = os.path.abspath(path)
55 with open(path) as fd:
56 config = yaml.load(fd)
57 return config
58
59
60banner = r'''
61 ____ __ __ _ ____ __ _ _
62( _ \ / \ ( ( \/ ___)( )( \/ )
63 ) __/( O )/ /\___ \ )( / \/ \
64(__) \__/ \_)__)(____/(__)\_)(_/
65'''
66
67def print_banner(log):
68 for line in banner.strip('\n').splitlines():
69 log.info(line)
70 log.info('(to stop: press Ctrl-C)')
71
72
73def parse_args():
74
75 parser = argparse.ArgumentParser()
76
77 _help = ('Path to chameleon.yml config file (default: %s). '
78 'If relative, it is relative to main.py of chameleon.'
79 % defs['config'])
80 parser.add_argument('-c', '--config',
81 dest='config',
82 action='store',
83 default=defs['config'],
84 help=_help)
85
86 _help = ('port number of the GRPC service exposed by voltha (default: %s)'
87 % defs['grpc_port'])
88 parser.add_argument('-g', '--grpc-port',
89 dest='grpc_port',
90 action='store',
91 default=defs['grpc_port'],
92 help=_help)
93
94 _help = ('number of ONUs to simulate (default: %d)' % defs['onus'])
95 parser.add_argument('-o', '--onus',
96 dest='onus',
97 action='store',
98 type=int,
99 default=defs['onus'],
100 help=_help)
101
102 _help = ('name of the PON natework used as a prefix for all network'
103 ' resource created on behalf of the PON (default: %s)' %
104 defs['name'])
105 parser.add_argument('-N', '--name',
106 dest='name',
107 action='store',
108 default=defs['name'],
109 help=_help)
110
111 _help = "suppress debug and info logs"
112 parser.add_argument('-q', '--quiet',
113 dest='quiet',
114 action='count',
115 help=_help)
116
117 _help = 'enable verbose logging'
118 parser.add_argument('-v', '--verbose',
119 dest='verbose',
120 action='count',
121 help=_help)
122
Stephane Barbarie4475a252017-03-31 13:49:20 -0400123 _help = 'enable generation of simulated alarms'
124 parser.add_argument('-a', '--alarm-simulation',
125 dest='alarm_simulation',
126 action='store_true',
127 default=False,
128 help=_help)
129
130 _help = 'frequency of simulated alarms (in seconds)'
131 parser.add_argument('-f', '--alarm-frequency',
132 dest='alarm_frequency',
133 action='store',
134 type=int,
khenaidoo032d3302017-06-09 14:50:04 -0400135 metavar="[5-300]",
136 choices=range(5,301),
Stephane Barbarie4475a252017-03-31 13:49:20 -0400137 default=60,
138 help=_help)
139
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800140 _help = 'omit startup banner log lines'
141 parser.add_argument('-n', '--no-banner',
142 dest='no_banner',
143 action='store_true',
144 default=False,
145 help=_help)
146
Shad Ansarida0f3a42017-07-19 09:51:06 -0700147 _help = ('device type - ponsim or bal'
148 ' (default: %s)' % defs['device_type'])
149 parser.add_argument('-d', '--device_type',
150 dest='device_type',
151 action='store',
152 default=defs['device_type'],
153 help=_help)
154
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800155 args = parser.parse_args()
156
157 return args
158
159
160class Main(object):
161
162 def __init__(self):
163
164 self.args = args = parse_args()
165 self.config = load_config(args)
166
167 verbosity_adjust = (args.verbose or 0) - (args.quiet or 0)
168 self.log = setup_logging(self.config.get('logging', {}),
169 args.name,
170 verbosity_adjust=verbosity_adjust)
171
172 # components
173 self.io = None
174 self.ponsim = None
Nikolay Titov89004ec2017-06-19 18:22:42 -0400175 self.x_pon_sim = None
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800176 self.grpc_server = None
177
Stephane Barbarie4475a252017-03-31 13:49:20 -0400178 self.alarm_config = dict()
179 self.alarm_config['simulation'] = self.args.alarm_simulation
180 self.alarm_config['frequency'] = self.args.alarm_frequency
181
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800182 if not args.no_banner:
183 print_banner(self.log)
184
Shad Ansarida0f3a42017-07-19 09:51:06 -0700185 if args.device_type == 'ponsim':
186 grpc_services = [(add_PonSimServicer_to_server, ponsim_servicer.FlowUpdateHandler)]
187 elif args.device_type == 'bal':
188 grpc_services = [(add_BalServicer_to_server, bal_servicer.BalHandler)]
189 grpc_services.append((add_XPonSimServicer_to_server, ponsim_servicer.XPonHandler))
190
191 self.startup_components(grpc_services)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800192
193 def start(self):
194 self.start_reactor() # will not return except Keyboard interrupt
195
196 @inlineCallbacks
Shad Ansarida0f3a42017-07-19 09:51:06 -0700197 def startup_components(self, grpc_services):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800198 try:
199 self.log.info('starting-internal-components')
200
201 iface_map = self.setup_networking_assets(self.args.name,
202 self.args.onus)
203 self.io = yield RealIo(iface_map).start()
Stephane Barbarie4475a252017-03-31 13:49:20 -0400204 self.ponsim = PonSim(self.args.onus, self.io.egress, self.alarm_config)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800205 self.io.register_ponsim(self.ponsim)
206
Nikolay Titov89004ec2017-06-19 18:22:42 -0400207 self.x_pon_sim = XPonSim()
208
209 self.grpc_server = GrpcServer(self.args.grpc_port, self.ponsim, self.x_pon_sim)
Shad Ansarida0f3a42017-07-19 09:51:06 -0700210 yield self.grpc_server.start(grpc_services)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800211
212 self.log.info('started-internal-services')
213
214 except Exception, e:
215 self.log.exception('startup-failed', e=e)
216
217 @inlineCallbacks
218 def shutdown_components(self):
219 """Execute before the reactor is shut down"""
220 self.log.info('exiting-on-keyboard-interrupt')
Rouzbahan Rashidi-Tabrizi66aa41d2017-02-24 09:30:30 -0500221 try:
222 if self.io is not None:
223 yield self.io.stop()
224 self.teardown_networking_assets(self.args.name, self.args.onus)
225 if self.grpc_server is not None:
226 yield self.grpc_server.stop()
227 except Exception, e:
228 self.log.exception('shutdown-failed', e=e)
Rouzbahan Rashidi-Tabrizi4f42f632017-02-23 17:13:31 -0500229
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800230 def start_reactor(self):
231 from twisted.internet import reactor
232 reactor.callWhenRunning(
233 lambda: self.log.info('twisted-reactor-started'))
234 reactor.addSystemEventTrigger('before', 'shutdown',
235 self.shutdown_components)
236 reactor.run()
237
238 def setup_networking_assets(self, prefix, n_unis):
239 # setup veth pairs for NNI and each UNI, using prefix and port numbers
240 port_map = dict()
241 for portnum in [0] + range(128, 128 + n_unis):
242 external_name = '%s_%d' % (prefix, portnum)
243 internal_name = external_name + 'sim'
244 os.system('sudo ip link add dev {} type veth peer name {}'.format(
245 external_name, internal_name
246 ))
247 os.system('sudo ip link set {} up'.format(external_name))
248 os.system('sudo ip link set {} up'.format(internal_name))
249 if portnum == 0:
250 os.system('sudo brctl addif ponmgmt {}'.format(external_name))
251 port_map[portnum] = internal_name
252 return port_map
253
254 def teardown_networking_assets(self, prefix, n_unis):
255 # undo all the networking stuff
256 for portnum in [0] + range(128, 128 + n_unis):
257 external_name = '%s_%d' % (prefix, portnum)
258 os.system('sudo ip link del {}'.format(external_name))
259
260
261if __name__ == '__main__':
262 Main().start()