#! /usr/bin/python # Copyright 2014-2018 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, NotFoundError, SocketError from util import VlanUtil from visualisation.visualisation import Visualisation class DaemonState: """ Simple container for stuff to make for nicer syntax """ state = DaemonState() state.version = "0.7" state.banner = "Linaro VLANd version %s" % state.version state.starttime = time.time() state.vis = None os.environ['TZ'] = 'UTC' time.tzset() print '%s' % state.banner print 'Parsing Config...' state.config = VlanConfig(filenames=('./vland.cfg',)) print ' Config knows about %d switches' % len(state.config.switches) if state.config.visualisation.enabled: state.vis = Visualisation(state) util = VlanUtil() loglevel = util.set_logging_level(state.config.logging.level) # 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 MAIN %(message)s', datefmt = '%Y-%m-%d %H:%M:%S %Z', filename = state.config.logging.filename, filemode = 'a') logging.info('%s main daemon starting up', state.banner) logging.info('Connecting to DB...') state.db = VlanDB(db_name=state.config.database.dbname, username=state.config.database.username, readonly=False) 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)) # 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 vland-admin auto_import_switch --name ' print 'for each of your switches may help!' print # Now we're done starting up, let the visualisation code talk to the database if state.config.visualisation.enabled: logging.info('sending db_ok signal') state.vis.signal_db_ok() # 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__() except NotFoundError as e: print e response['response'] = 'NOTFOUND' 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__() except NotFoundError as e: print e response['response'] = 'NOTFOUND' 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__() except NotFoundError as e: print e response['response'] = 'NOTFOUND' 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__() except NotFoundError as e: print e response['response'] = 'NOTFOUND' 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() if state.config.visualisation.enabled: state.vis.shutdown() 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()