blob: f3d0ffdc97a6456290dda1491758ce10ecc8757a [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
116 _help = 'omit startup banner log lines'
117 parser.add_argument('-n', '--no-banner',
118 dest='no_banner',
119 action='store_true',
120 default=False,
121 help=_help)
122
123 args = parser.parse_args()
124
125 return args
126
127
128class Main(object):
129
130 def __init__(self):
131
132 self.args = args = parse_args()
133 self.config = load_config(args)
134
135 verbosity_adjust = (args.verbose or 0) - (args.quiet or 0)
136 self.log = setup_logging(self.config.get('logging', {}),
137 args.name,
138 verbosity_adjust=verbosity_adjust)
139
140 # components
141 self.io = None
142 self.ponsim = None
143 self.grpc_server = None
144
145 if not args.no_banner:
146 print_banner(self.log)
147
148 self.startup_components()
149
150 def start(self):
151 self.start_reactor() # will not return except Keyboard interrupt
152
153 @inlineCallbacks
154 def startup_components(self):
155 try:
156 self.log.info('starting-internal-components')
157
158 iface_map = self.setup_networking_assets(self.args.name,
159 self.args.onus)
160 self.io = yield RealIo(iface_map).start()
161 self.ponsim = PonSim(self.args.onus, self.io.egress)
162 self.io.register_ponsim(self.ponsim)
163
164 self.grpc_server = GrpcServer(self.args.grpc_port, self.ponsim)
165 yield self.grpc_server.start()
166
167 self.log.info('started-internal-services')
168
169 except Exception, e:
170 self.log.exception('startup-failed', e=e)
171
172 @inlineCallbacks
173 def shutdown_components(self):
174 """Execute before the reactor is shut down"""
175 self.log.info('exiting-on-keyboard-interrupt')
176 if self.io is not None:
177 self.io.stop()
178 self.teardown_networking_assets(self.args.name, self.args.onus)
179 if self.grpc_server is not None:
180 yield self.grpc_server.stop()
181
182 def start_reactor(self):
183 from twisted.internet import reactor
184 reactor.callWhenRunning(
185 lambda: self.log.info('twisted-reactor-started'))
186 reactor.addSystemEventTrigger('before', 'shutdown',
187 self.shutdown_components)
188 reactor.run()
189
190 def setup_networking_assets(self, prefix, n_unis):
191 # setup veth pairs for NNI and each UNI, using prefix and port numbers
192 port_map = dict()
193 for portnum in [0] + range(128, 128 + n_unis):
194 external_name = '%s_%d' % (prefix, portnum)
195 internal_name = external_name + 'sim'
196 os.system('sudo ip link add dev {} type veth peer name {}'.format(
197 external_name, internal_name
198 ))
199 os.system('sudo ip link set {} up'.format(external_name))
200 os.system('sudo ip link set {} up'.format(internal_name))
201 if portnum == 0:
202 os.system('sudo brctl addif ponmgmt {}'.format(external_name))
203 port_map[portnum] = internal_name
204 return port_map
205
206 def teardown_networking_assets(self, prefix, n_unis):
207 # undo all the networking stuff
208 for portnum in [0] + range(128, 128 + n_unis):
209 external_name = '%s_%d' % (prefix, portnum)
210 os.system('sudo ip link del {}'.format(external_name))
211
212
213if __name__ == '__main__':
214 Main().start()