blob: 1b11407a94158b853bf2d01d129e7d5c6221274e [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2016 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
PON Simulator process, able to move packets across NNI and UNIs, as well
as take MGMT calls via gRPC.
It can only work on Linux.
"""
import argparse
import os
import yaml
from twisted.internet.defer import inlineCallbacks
from common.structlog_setup import setup_logging
from grpc_server import GrpcServer
from ponsim import PonSim
from realio import RealIo
defs = dict(
config=os.environ.get('CONFIG', './ponsim.yml'),
grpc_port=int(os.environ.get('GRPC_PORT', 50060)),
name=os.environ.get('NAME', 'pon1'),
onus=int(os.environ.get("ONUS", 1))
)
def load_config(args):
path = args.config
if path.startswith('.'):
dir = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(dir, path)
path = os.path.abspath(path)
with open(path) as fd:
config = yaml.load(fd)
return config
banner = r'''
____ __ __ _ ____ __ _ _
( _ \ / \ ( ( \/ ___)( )( \/ )
) __/( O )/ /\___ \ )( / \/ \
(__) \__/ \_)__)(____/(__)\_)(_/
'''
def print_banner(log):
for line in banner.strip('\n').splitlines():
log.info(line)
log.info('(to stop: press Ctrl-C)')
def parse_args():
parser = argparse.ArgumentParser()
_help = ('Path to chameleon.yml config file (default: %s). '
'If relative, it is relative to main.py of chameleon.'
% defs['config'])
parser.add_argument('-c', '--config',
dest='config',
action='store',
default=defs['config'],
help=_help)
_help = ('port number of the GRPC service exposed by voltha (default: %s)'
% defs['grpc_port'])
parser.add_argument('-g', '--grpc-port',
dest='grpc_port',
action='store',
default=defs['grpc_port'],
help=_help)
_help = ('number of ONUs to simulate (default: %d)' % defs['onus'])
parser.add_argument('-o', '--onus',
dest='onus',
action='store',
type=int,
default=defs['onus'],
help=_help)
_help = ('name of the PON natework used as a prefix for all network'
' resource created on behalf of the PON (default: %s)' %
defs['name'])
parser.add_argument('-N', '--name',
dest='name',
action='store',
default=defs['name'],
help=_help)
_help = "suppress debug and info logs"
parser.add_argument('-q', '--quiet',
dest='quiet',
action='count',
help=_help)
_help = 'enable verbose logging'
parser.add_argument('-v', '--verbose',
dest='verbose',
action='count',
help=_help)
_help = 'enable generation of simulated alarms'
parser.add_argument('-a', '--alarm-simulation',
dest='alarm_simulation',
action='store_true',
default=False,
help=_help)
_help = 'frequency of simulated alarms (in seconds)'
parser.add_argument('-f', '--alarm-frequency',
dest='alarm_frequency',
action='store',
type=int,
metavar="[5-300]",
choices=range(5,301),
default=60,
help=_help)
_help = 'omit startup banner log lines'
parser.add_argument('-n', '--no-banner',
dest='no_banner',
action='store_true',
default=False,
help=_help)
args = parser.parse_args()
return args
class Main(object):
def __init__(self):
self.args = args = parse_args()
self.config = load_config(args)
verbosity_adjust = (args.verbose or 0) - (args.quiet or 0)
self.log = setup_logging(self.config.get('logging', {}),
args.name,
verbosity_adjust=verbosity_adjust)
# components
self.io = None
self.ponsim = None
self.grpc_server = None
self.alarm_config = dict()
self.alarm_config['simulation'] = self.args.alarm_simulation
self.alarm_config['frequency'] = self.args.alarm_frequency
if not args.no_banner:
print_banner(self.log)
self.startup_components()
def start(self):
self.start_reactor() # will not return except Keyboard interrupt
@inlineCallbacks
def startup_components(self):
try:
self.log.info('starting-internal-components')
iface_map = self.setup_networking_assets(self.args.name,
self.args.onus)
self.io = yield RealIo(iface_map).start()
self.ponsim = PonSim(self.args.onus, self.io.egress, self.alarm_config)
self.io.register_ponsim(self.ponsim)
self.grpc_server = GrpcServer(self.args.grpc_port, self.ponsim)
yield self.grpc_server.start()
self.log.info('started-internal-services')
except Exception, e:
self.log.exception('startup-failed', e=e)
@inlineCallbacks
def shutdown_components(self):
"""Execute before the reactor is shut down"""
self.log.info('exiting-on-keyboard-interrupt')
try:
if self.io is not None:
yield self.io.stop()
self.teardown_networking_assets(self.args.name, self.args.onus)
if self.grpc_server is not None:
yield self.grpc_server.stop()
except Exception, e:
self.log.exception('shutdown-failed', e=e)
def start_reactor(self):
from twisted.internet import reactor
reactor.callWhenRunning(
lambda: self.log.info('twisted-reactor-started'))
reactor.addSystemEventTrigger('before', 'shutdown',
self.shutdown_components)
reactor.run()
def setup_networking_assets(self, prefix, n_unis):
# setup veth pairs for NNI and each UNI, using prefix and port numbers
port_map = dict()
for portnum in [0] + range(128, 128 + n_unis):
external_name = '%s_%d' % (prefix, portnum)
internal_name = external_name + 'sim'
os.system('sudo ip link add dev {} type veth peer name {}'.format(
external_name, internal_name
))
os.system('sudo ip link set {} up'.format(external_name))
os.system('sudo ip link set {} up'.format(internal_name))
if portnum == 0:
os.system('sudo brctl addif ponmgmt {}'.format(external_name))
port_map[portnum] = internal_name
return port_map
def teardown_networking_assets(self, prefix, n_unis):
# undo all the networking stuff
for portnum in [0] + range(128, 128 + n_unis):
external_name = '%s_%d' % (prefix, portnum)
os.system('sudo ip link del {}'.format(external_name))
if __name__ == '__main__':
Main().start()