| #! /usr/bin/python |
| |
| # Copyright 2014 Linaro Limited |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program; if not, write to the Free Software |
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
| # MA 02110-1301, USA. |
| # |
| # Main VLANd module |
| # |
| |
| import os, sys |
| import time |
| import logging |
| |
| vlandpath = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0]))) |
| sys.path.insert(0, vlandpath) |
| |
| from config.config import VlanConfig |
| from db.db import VlanDB |
| from ipc.ipc import VlanIpc |
| from errors import InputError, SocketError |
| from util import VlanUtil |
| |
| class DaemonState: |
| """ Simple container for stuff to make for nicer syntax """ |
| |
| state = DaemonState() |
| state.version = "0.4-DEV" |
| state.banner = "Linaro VLANd version %s" % state.version |
| state.starttime = time.time() |
| |
| print '%s' % state.banner |
| |
| print 'Parsing Config...' |
| state.config = VlanConfig(filenames=('./vland.cfg',)) |
| print ' Config knows about %d switches' % len(state.config.switches) |
| |
| util = VlanUtil() |
| |
| print 'Connecting to DB...' |
| state.db = VlanDB(db_name=state.config.database.dbname, |
| username=state.config.database.username) |
| |
| switches = state.db.all_switches() |
| print ' DB knows about %d switches' % len(switches) |
| ports = state.db.all_ports() |
| print ' DB knows about %d ports' % len(ports) |
| vlans = state.db.all_vlans() |
| print ' DB knows about %d vlans' % len(vlans) |
| trunks = state.db.all_trunks() |
| print ' DB knows about %d trunks' % len(trunks) |
| |
| # Initial startup sanity chacking |
| |
| # For sanity, we need to know the vlan_id for the default vlan (tag |
| # 1). Make sure we know that before anybody attempts to create things |
| # that depend on it. |
| state.default_vlan_id = state.db.get_vlan_id_by_tag(state.config.vland.default_vlan_tag) |
| if state.default_vlan_id is None: |
| # It doesn't exist - create it and try again |
| state.default_vlan_id = state.db.create_vlan("DEFAULT", |
| state.config.vland.default_vlan_tag, |
| True) |
| |
| if len(switches) != len(state.config.switches): |
| print 'You have configured access details for %d switch(es), ' % len(state.config.switches) |
| print 'but have %d switch(es) registered in your database.' % len(switches) |
| print 'You must fix this difference for VLANd to work sensibly.' |
| print 'HINT: Running admin.py --auto_import_switch <switch_name>' |
| print 'for each of your switches may help!' |
| print |
| |
| # Set up logging and maybe go quiet... |
| if state.config.logging.level is None: |
| state.config.logging.level = "CRITICAL" |
| |
| loglevel = logging.CRITICAL |
| if state.config.logging.level == "ERROR": |
| loglevel = logging.ERROR |
| elif state.config.logging.level == "WARNING": |
| loglevel = logging.WARNING |
| elif state.config.logging.level == "INFO": |
| loglevel = logging.INFO |
| elif state.config.logging.level == "DEBUG": |
| loglevel = logging.DEBUG |
| |
| os.environ['TZ'] = 'UTC' |
| time.tzset() |
| |
| # Should we log to stderr? |
| if state.config.logging.filename is None: |
| logging.basicConfig(level = loglevel, |
| format = '%(asctime)s %(levelname)-8s %(message)s') |
| else: |
| print "Logging to %s" % state.config.logging.filename |
| logging.basicConfig(level = loglevel, |
| format = '%(asctime)s %(levelname)-8s %(message)s', |
| datefmt = '%Y-%m-%d %H:%M:%S %Z', |
| filename = state.config.logging.filename, |
| filemode = 'a') |
| logging.info('%s starting up', state.banner) |
| switches = state.db.all_switches() |
| logging.info(' DB knows about %d switches', len(switches)) |
| ports = state.db.all_ports() |
| logging.info(' DB knows about %d ports', len(ports)) |
| vlans = state.db.all_vlans() |
| logging.info(' DB knows about %d vlans', len(vlans)) |
| trunks = state.db.all_trunks() |
| logging.info(' DB knows about %d trunks', len(trunks)) |
| |
| # Now start up the core loop. Listen for command connections and |
| # process them |
| state.running = True |
| ipc = VlanIpc() |
| ipc.server_init('localhost', state.config.vland.port) |
| while state.running: |
| try: |
| ipc.server_listen() |
| json_data = ipc.server_recv() |
| except SocketError as e: |
| print e |
| logging.debug('Caught IPC error, ignoring') |
| continue |
| except: |
| ipc.server_close() |
| raise |
| |
| logging.debug("client %s sent us:", json_data['client_name']) |
| logging.debug(json_data) |
| |
| response = {} |
| |
| # Several types of IPC message here, with potentially different |
| # access control and safety |
| |
| # First - simple queries to the database only. Should be safe! |
| if json_data['type'] == 'db_query': |
| response['type'] = 'response' |
| try: |
| response['data'] = util.perform_db_query(state, json_data['command'], json_data['data']) |
| response['response'] = 'ACK' |
| except InputError as e: |
| print e |
| response['response'] = 'ERROR' |
| response['error'] = e.__str__() |
| |
| # Next - simple queries about daemon state only. Should be safe! |
| if json_data['type'] == 'daemon_query': |
| response['type'] = 'response' |
| try: |
| response['data'] = util.perform_daemon_query(state, json_data['command'], json_data['data']) |
| response['response'] = 'ACK' |
| except InputError as e: |
| print e |
| response['response'] = 'ERROR' |
| response['error'] = e.__str__() |
| |
| # Next, calls that manipulate objects in the database only |
| # (switches and ports). These are safe and don't need actual |
| # co-ordinating with hardware directly. |
| # |
| # As/when/if we add authentication, use of this function will need |
| # it. |
| if json_data['type'] == 'db_update': |
| response['type'] = 'response' |
| try: |
| response['data'] = util.perform_db_update(state, json_data['command'], json_data['data']) |
| response['response'] = 'ACK' |
| except InputError as e: |
| print e |
| response['response'] = 'ERROR' |
| response['error'] = e.__str__() |
| |
| # Next, calls that may manipulate switch state *as well* as state |
| # in the database - changes to VLAN setup. |
| # |
| # As/when/if we add authentication, use of this function will need |
| # it. |
| if json_data['type'] == 'vlan_update': |
| response['type'] = 'response' |
| try: |
| response['data'] = util.perform_vlan_update(state, json_data['command'], json_data['data']) |
| response['response'] = 'ACK' |
| except InputError as e: |
| print e |
| response['response'] = 'ERROR' |
| response['error'] = e.__str__() |
| |
| # Finally, IPC interface for more complex API calls. |
| # NOT IMPLEMENTED YET |
| if json_data['type'] == 'vland_api': |
| response['type'] = 'response' |
| response['response'] = 'ERROR' |
| response['error'] = 'VLANd API not yet implemented...' |
| |
| logging.debug("sending reply:") |
| logging.debug(response) |
| |
| ipc.server_reply(response) |
| |
| # We've been asked to shut down. Do that as cleanly as we can |
| ipc.server_close() |
| |
| logging.info('%s shutting down', state.banner) |
| switches = state.db.all_switches() |
| logging.info(' DB knows about %d switches', len(switches)) |
| ports = state.db.all_ports() |
| logging.info(' DB knows about %d ports', len(ports)) |
| vlans = state.db.all_vlans() |
| logging.info(' DB knows about %d vlans', len(vlans)) |
| trunks = state.db.all_trunks() |
| logging.info(' DB knows about %d trunks', len(trunks)) |
| |
| logging.shutdown() |