blob: 2100879d09fd624b1766e0150c191af209d430f5 [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
34defs = dict(
35 config=os.environ.get('CONFIG', './ponsim.yml'),
36 grpc_port=int(os.environ.get('GRPC_PORT', 50060)),
37 name=os.environ.get('NAME', 'pon1'),
38 onus=int(os.environ.get("ONUS", 1))
39)
40
41
42def load_config(args):
43 path = args.config
44 if path.startswith('.'):
45 dir = os.path.dirname(os.path.abspath(__file__))
46 path = os.path.join(dir, path)
47 path = os.path.abspath(path)
48 with open(path) as fd:
49 config = yaml.load(fd)
50 return config
51
52
53banner = r'''
54 ____ __ __ _ ____ __ _ _
55( _ \ / \ ( ( \/ ___)( )( \/ )
56 ) __/( O )/ /\___ \ )( / \/ \
57(__) \__/ \_)__)(____/(__)\_)(_/
58'''
59
60def print_banner(log):
61 for line in banner.strip('\n').splitlines():
62 log.info(line)
63 log.info('(to stop: press Ctrl-C)')
64
65
66def parse_args():
67
68 parser = argparse.ArgumentParser()
69
70 _help = ('Path to chameleon.yml config file (default: %s). '
71 'If relative, it is relative to main.py of chameleon.'
72 % defs['config'])
73 parser.add_argument('-c', '--config',
74 dest='config',
75 action='store',
76 default=defs['config'],
77 help=_help)
78
79 _help = ('port number of the GRPC service exposed by voltha (default: %s)'
80 % defs['grpc_port'])
81 parser.add_argument('-g', '--grpc-port',
82 dest='grpc_port',
83 action='store',
84 default=defs['grpc_port'],
85 help=_help)
86
87 _help = ('number of ONUs to simulate (default: %d)' % defs['onus'])
88 parser.add_argument('-o', '--onus',
89 dest='onus',
90 action='store',
91 type=int,
92 default=defs['onus'],
93 help=_help)
94
95 _help = ('name of the PON natework used as a prefix for all network'
96 ' resource created on behalf of the PON (default: %s)' %
97 defs['name'])
98 parser.add_argument('-N', '--name',
99 dest='name',
100 action='store',
101 default=defs['name'],
102 help=_help)
103
104 _help = "suppress debug and info logs"
105 parser.add_argument('-q', '--quiet',
106 dest='quiet',
107 action='count',
108 help=_help)
109
110 _help = 'enable verbose logging'
111 parser.add_argument('-v', '--verbose',
112 dest='verbose',
113 action='count',
114 help=_help)
115
Stephane Barbarie4475a252017-03-31 13:49:20 -0400116 _help = 'enable generation of simulated alarms'
117 parser.add_argument('-a', '--alarm-simulation',
118 dest='alarm_simulation',
119 action='store_true',
120 default=False,
121 help=_help)
122
123 _help = 'frequency of simulated alarms (in seconds)'
124 parser.add_argument('-f', '--alarm-frequency',
125 dest='alarm_frequency',
126 action='store',
127 type=int,
128 metavar="[30-300]",
129 choices=range(30,301),
130 default=60,
131 help=_help)
132
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800133 _help = 'omit startup banner log lines'
134 parser.add_argument('-n', '--no-banner',
135 dest='no_banner',
136 action='store_true',
137 default=False,
138 help=_help)
139
140 args = parser.parse_args()
141
142 return args
143
144
145class Main(object):
146
147 def __init__(self):
148
149 self.args = args = parse_args()
150 self.config = load_config(args)
151
152 verbosity_adjust = (args.verbose or 0) - (args.quiet or 0)
153 self.log = setup_logging(self.config.get('logging', {}),
154 args.name,
155 verbosity_adjust=verbosity_adjust)
156
157 # components
158 self.io = None
159 self.ponsim = None
160 self.grpc_server = None
161
Stephane Barbarie4475a252017-03-31 13:49:20 -0400162 self.alarm_config = dict()
163 self.alarm_config['simulation'] = self.args.alarm_simulation
164 self.alarm_config['frequency'] = self.args.alarm_frequency
165
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800166 if not args.no_banner:
167 print_banner(self.log)
168
169 self.startup_components()
170
171 def start(self):
172 self.start_reactor() # will not return except Keyboard interrupt
173
174 @inlineCallbacks
175 def startup_components(self):
176 try:
177 self.log.info('starting-internal-components')
178
179 iface_map = self.setup_networking_assets(self.args.name,
180 self.args.onus)
181 self.io = yield RealIo(iface_map).start()
Stephane Barbarie4475a252017-03-31 13:49:20 -0400182 self.ponsim = PonSim(self.args.onus, self.io.egress, self.alarm_config)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800183 self.io.register_ponsim(self.ponsim)
184
185 self.grpc_server = GrpcServer(self.args.grpc_port, self.ponsim)
186 yield self.grpc_server.start()
187
188 self.log.info('started-internal-services')
189
190 except Exception, e:
191 self.log.exception('startup-failed', e=e)
192
193 @inlineCallbacks
194 def shutdown_components(self):
195 """Execute before the reactor is shut down"""
196 self.log.info('exiting-on-keyboard-interrupt')
Rouzbahan Rashidi-Tabrizi66aa41d2017-02-24 09:30:30 -0500197 try:
198 if self.io is not None:
199 yield self.io.stop()
200 self.teardown_networking_assets(self.args.name, self.args.onus)
201 if self.grpc_server is not None:
202 yield self.grpc_server.stop()
203 except Exception, e:
204 self.log.exception('shutdown-failed', e=e)
Rouzbahan Rashidi-Tabrizi4f42f632017-02-23 17:13:31 -0500205
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800206 def start_reactor(self):
207 from twisted.internet import reactor
208 reactor.callWhenRunning(
209 lambda: self.log.info('twisted-reactor-started'))
210 reactor.addSystemEventTrigger('before', 'shutdown',
211 self.shutdown_components)
212 reactor.run()
213
214 def setup_networking_assets(self, prefix, n_unis):
215 # setup veth pairs for NNI and each UNI, using prefix and port numbers
216 port_map = dict()
217 for portnum in [0] + range(128, 128 + n_unis):
218 external_name = '%s_%d' % (prefix, portnum)
219 internal_name = external_name + 'sim'
220 os.system('sudo ip link add dev {} type veth peer name {}'.format(
221 external_name, internal_name
222 ))
223 os.system('sudo ip link set {} up'.format(external_name))
224 os.system('sudo ip link set {} up'.format(internal_name))
225 if portnum == 0:
226 os.system('sudo brctl addif ponmgmt {}'.format(external_name))
227 port_map[portnum] = internal_name
228 return port_map
229
230 def teardown_networking_assets(self, prefix, n_unis):
231 # undo all the networking stuff
232 for portnum in [0] + range(128, 128 + n_unis):
233 external_name = '%s_%d' % (prefix, portnum)
234 os.system('sudo ip link del {}'.format(external_name))
235
236
237if __name__ == '__main__':
238 Main().start()