"""Utility functions for FAUCET."""
# Copyright (C) 2015 Brad Cowie, Christopher Lorier and Joe Stringer.
# Copyright (C) 2015 Research and Education Advanced Network New Zealand Ltd.
# Copyright (C) 2015--2019 The Contributors
#
# 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.
import logging
from logging.handlers import WatchedFileHandler
import os
import signal
import sys
from functools import wraps
[docs]
def kill_on_exception(logname):
"""decorator to ensure functions will kill ryu when an unhandled exception
occurs"""
def _koe(func):
@wraps(func)
def __koe(*args, **kwargs):
try:
func(*args, **kwargs)
except Exception: # pylint: disable=broad-except
logging.getLogger(logname).exception("Unhandled exception, killing RYU")
logging.shutdown()
os.kill(os.getpid(), signal.SIGTERM)
return __koe
return _koe
[docs]
def utf8_decode(msg_str):
"""Gracefully decode a possibly UTF-8 string."""
return msg_str.decode("utf-8", errors="replace")
[docs]
def get_sys_prefix():
"""Returns an additional prefix for log and configuration files when used in
a virtual environment"""
# Find the appropriate prefix for config and log file default locations
# in case Faucet is run in a virtual environment. virtualenv marks the
# original path in sys.real_prefix. If this value exists, and is
# different from sys.prefix, then we are most likely running in a
# virtualenv. Also check for Py3.3+ pyvenv.
sysprefix = ""
if (
getattr(sys, "real_prefix", sys.prefix) != sys.prefix
or getattr(sys, "base_prefix", sys.prefix) != sys.prefix
):
sysprefix = sys.prefix
return sysprefix
_PREFIX = get_sys_prefix()
# To specify a boolean-only setting, set the default value to a bool type.
DEFAULTS = {
"FAUCET_CONFIG": "".join(
(
_PREFIX,
"/etc/faucet/faucet.yaml",
":",
_PREFIX,
"/etc/ryu/faucet/faucet.yaml",
)
),
"FAUCET_STACK_ROOT_STATE_UPDATE_TIME": 10,
"FAUCET_CONFIG_STAT_RELOAD": False,
"FAUCET_CONFIG_AUTO_REVERT": False,
"FAUCET_LOG_LEVEL": "INFO",
"FAUCET_LOG": _PREFIX + "/var/log/faucet/faucet.log",
"FAUCET_EVENT_SOCK": "", # Special-case, see get_setting().
"FAUCET_EVENT_SOCK_HEARTBEAT": 0, # Special-case, see get_setting().
"FAUCET_EXCEPTION_LOG": _PREFIX + "/var/log/faucet/faucet_exception.log",
"FAUCET_PROMETHEUS_PORT": "9302",
"FAUCET_PROMETHEUS_ADDR": "0.0.0.0",
"GAUGE_CONFIG": "".join(
(_PREFIX, "/etc/faucet/gauge.yaml", ":", _PREFIX, "/etc/ryu/faucet/gauge.yaml")
),
"GAUGE_CONFIG_STAT_RELOAD": False,
"GAUGE_LOG_LEVEL": "INFO",
"GAUGE_PROMETHEUS_ADDR": "0.0.0.0",
"GAUGE_EXCEPTION_LOG": _PREFIX + "/var/log/faucet/gauge_exception.log",
"GAUGE_LOG": _PREFIX + "/var/log/faucet/gauge.log",
}
def _cast_bool(value):
"""Return True if value is a non-zero int."""
try:
return int(value) != 0
except ValueError:
return False
[docs]
def get_setting(name, path_eval=False):
"""Returns value of specified configuration setting."""
default_value = DEFAULTS[name]
result = os.getenv(name, default_value)
# split on ':' and find the first suitable path
if (
path_eval
and isinstance(result, str)
and isinstance(default_value, str)
and not isinstance(default_value, bool)
):
locations = result.split(":")
result = None
for loc in locations:
if os.path.isfile(loc):
result = loc
break
if result is None:
result = locations[0]
# Check for setting that expects a boolean result.
if isinstance(default_value, bool):
return _cast_bool(result)
# Special default for FAUCET_EVENT_SOCK.
if name == "FAUCET_EVENT_SOCK":
if result == "0":
return ""
if _cast_bool(result):
return _PREFIX + "/var/run/faucet/faucet.sock"
if name == "FAUCET_EVENT_SOCK_HEARTBEAT":
if result == "0":
return 0
return result
[docs]
def get_logger(logname, logfile, loglevel, propagate):
"""Create and return a logger object."""
stream_handlers = {
"STDOUT": sys.stdout,
"STDERR": sys.stderr,
}
try:
if logfile in stream_handlers:
logger_handler = logging.StreamHandler(stream_handlers[logfile])
else:
logger_handler = WatchedFileHandler(logfile)
except (PermissionError, FileNotFoundError) as err: # pytype: disable=name-error
print(err)
sys.exit(-1)
logger = logging.getLogger(logname)
log_fmt = "%(asctime)s %(name)-6s %(levelname)-8s %(message)s"
logger_handler.setFormatter(logging.Formatter(log_fmt, "%b %d %H:%M:%S"))
logger.addHandler(logger_handler)
logger.propagate = propagate
logger.setLevel(loglevel)
return logger
[docs]
def close_logger(logger):
"""Close all handlers on logger object."""
if logger is None:
return
for handler in list(logger.handlers):
handler.close()
logger.removeHandler(handler)
[docs]
def dpid_log(dpid):
"""Log a DP ID as hex/decimal."""
if dpid is None:
return "DPID None (NoneType)"
return "DPID %u (0x%x)" % (dpid, dpid)
[docs]
def stat_config_files(config_hashes):
"""Return dict of a subset of stat attributes on config files."""
config_files_stats = {}
for config_file in list(config_hashes.keys()):
try:
config_file_stat = os.stat(config_file)
except OSError:
continue
config_files_stats[config_file] = (
config_file_stat.st_size,
config_file_stat.st_mtime,
config_file_stat.st_ctime,
)
return config_files_stats