blob: b6e305e6a89f7240ac7ca8a5f270bb49d91513f8 [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')
Rouzbahan Rashidi-Tabrizi66aa41d2017-02-24 09:30:30 -0500176 try:
177 if self.io is not None:
178 yield self.io.stop()
179 self.teardown_networking_assets(self.args.name, self.args.onus)
180 if self.grpc_server is not None:
181 yield self.grpc_server.stop()
182 except Exception, e:
183 self.log.exception('shutdown-failed', e=e)
Rouzbahan Rashidi-Tabrizi4f42f632017-02-23 17:13:31 -0500184
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800185 def start_reactor(self):
186 from twisted.internet import reactor
187 reactor.callWhenRunning(
188 lambda: self.log.info('twisted-reactor-started'))
189 reactor.addSystemEventTrigger('before', 'shutdown',
190 self.shutdown_components)
191 reactor.run()
192
193 def setup_networking_assets(self, prefix, n_unis):
194 # setup veth pairs for NNI and each UNI, using prefix and port numbers
195 port_map = dict()
196 for portnum in [0] + range(128, 128 + n_unis):
197 external_name = '%s_%d' % (prefix, portnum)
198 internal_name = external_name + 'sim'
199 os.system('sudo ip link add dev {} type veth peer name {}'.format(
200 external_name, internal_name
201 ))
202 os.system('sudo ip link set {} up'.format(external_name))
203 os.system('sudo ip link set {} up'.format(internal_name))
204 if portnum == 0:
205 os.system('sudo brctl addif ponmgmt {}'.format(external_name))
206 port_map[portnum] = internal_name
207 return port_map
208
209 def teardown_networking_assets(self, prefix, n_unis):
210 # undo all the networking stuff
211 for portnum in [0] + range(128, 128 + n_unis):
212 external_name = '%s_%d' % (prefix, portnum)
213 os.system('sudo ip link del {}'.format(external_name))
214
215
216if __name__ == '__main__':
217 Main().start()