blob: 7365a73c7f1562cc46ffbff79335192879e6e99c [file] [log] [blame]
#! /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.
#
# Top-level VLANd daemon
#
import os, sys
import time
import logging
from Vland.config.config import VlanConfig
from Vland.db.db import VlanDB
from Vland.ipc.ipc import VlanIpc
from Vland.errors import InputError, NotFoundError, SocketError
from Vland.util import VlanUtil
from Vland.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 <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()