src/lib/log/tests/severity_test.sh
src/lib/log/tests/tempdir.h
src/lib/Makefile
- src/lib/python/bind10_config.py
- src/lib/python/isc/bind10/Makefile
- src/lib/python/isc/bind10/tests/Makefile
- src/lib/python/isc/cc/cc_generated/Makefile
- src/lib/python/isc/cc/Makefile
- src/lib/python/isc/cc/tests/cc_test
- src/lib/python/isc/cc/tests/Makefile
- src/lib/python/isc/config/Makefile
- src/lib/python/isc/config/tests/config_test
- src/lib/python/isc/config/tests/Makefile
- src/lib/python/isc/dns/Makefile
- src/lib/python/isc/log/Makefile
- src/lib/python/isc/log_messages/Makefile
- src/lib/python/isc/log_messages/work/__init__.py
- src/lib/python/isc/log_messages/work/Makefile
- src/lib/python/isc/log/tests/log_console.py
- src/lib/python/isc/log/tests/Makefile
- src/lib/python/isc/Makefile
- src/lib/python/isc/net/Makefile
- src/lib/python/isc/net/tests/Makefile
- src/lib/python/isc/sysinfo/Makefile
- src/lib/python/isc/sysinfo/tests/Makefile
- src/lib/python/isc/testutils/Makefile
- src/lib/python/isc/util/cio/Makefile
- src/lib/python/isc/util/cio/tests/Makefile
- src/lib/python/isc/util/Makefile
- src/lib/python/isc/util/tests/Makefile
- src/lib/python/Makefile
src/lib/testutils/Makefile
src/lib/testutils/testdata/Makefile
src/lib/util/io/Makefile
# The following build order must be maintained.
SUBDIRS = exceptions util log hooks cryptolink dns cc config \
- asiolink asiodns testutils python dhcp dhcp_ddns \
+ asiolink asiodns testutils dhcp dhcp_ddns \
dhcpsrv
+++ /dev/null
-/bind10_config.py
+++ /dev/null
-SUBDIRS = isc
-
-nodist_python_PYTHON = bind10_config.py
-pythondir = $(pyexecdir)
-
-CLEANFILES = bind10_config.pyc bind10_config.pyo
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-# This is a base-level module intended to provide configure-time
-# variables to python scripts and libraries.
-import os
-
-def get_specfile_location(module_name):
- """Return the path to the module spec file following common convetion.
-
- This method generates the path commonly used by most BIND 10
- modules, determined by a well known prefix and the module name.
-
- A specific module can override this method if it uses a different
- path for the spec file.
-
- """
- # First check if it's running under an 'in-source' environment,
- # then try commonly used paths and file names. If found, use it.
- for ev in ['B10_FROM_SOURCE', 'B10_FROM_BUILD']:
- if ev in os.environ:
- specfile = os.environ[ev] + '/src/bin/' + module_name + \
- '/' + module_name + '.spec'
- if os.path.exists(specfile):
- return specfile
- # Otherwise, just use the installed path, whether or not it really
- # exists; leave error handling to the caller.
- specfile_path = '@datadir@/@PACKAGE@'\
- .replace('${datarootdir}', '@datarootdir@')\
- .replace('${prefix}', '@prefix@')
- return specfile_path + '/' + module_name + '.spec'
-
-def reload():
- # In a function, for testing purposes
- global BIND10_MSGQ_SOCKET_FILE
- global DATA_PATH
- global PLUGIN_PATHS
- global PREFIX
- global LIBEXECPATH
- global SYSCONFPATH
- BIND10_MSGQ_SOCKET_FILE = os.path.join("@localstatedir@",
- "@PACKAGE_NAME@",
- "msgq_socket").replace("${prefix}",
- "@prefix@")
- PREFIX = "@prefix@"
- SYSCONFPATH="@sysconfdir@/@PACKAGE@".replace('${prefix}', PREFIX)
-
- # B10_FROM_SOURCE is set in the environment for internal tests and
- # an experimental run without installation. In that case we need to
- # specialize some configuration variables, generally so that they refer
- # to somewhere in the source tree instead of the appropriate places
- # after installation.
- #
- # DATA_PATH: used by the config manager to find configuration files.
- # When "FROM_SOURCE", we use data files from a directory relative to the
- # value of that variable, or, if defined, relative to the value of
- # B10_FROM_SOURCE_LOCALSTATEDIR. Otherwise we use the ones installed on
- # the system.
- # PLUGIN_PATHS: configuration modules that are not associated to specific
- # process
- # LIBEXECPATH: Paths to programs invoked by the b10-init process
- # The b10-init process (directly or via a helper module) uses this as
- # the prefererred PATH before starting a child process.
- # When "FROM_SOURCE", it lists the directories where the programs are
- # built so that when BIND 10 is experimentally started on the source
- # tree the programs in the tree (not installed ones) will be used.
- # SYSCONFPATH: Path where the system-wide configuration files are
- # stored (e.g. <prefix>/var/<package name>). This value is *not*
- # overwritten if B10_FROM_SOURCE is specified.
- #
- # B10_FROM_SOURCE_LOCALSTATEDIR is specifically intended to be used for
- # tests where we want to use various types of configuration within the test
- # environment. (We may want to make it even more generic so that the path
- # is passed from the b10-init process)
- if "B10_FROM_SOURCE" in os.environ:
- if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
- DATA_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
- else:
- DATA_PATH = os.environ["B10_FROM_SOURCE"]
- PLUGIN_PATHS = [os.environ["B10_FROM_SOURCE"] +
- '/src/bin/cfgmgr/local_plugins',
- os.environ["B10_FROM_SOURCE"] +
- '/src/bin/cfgmgr/plugins']
- programdirs = ['auth', 'cfgmgr', 'cmdctl', 'ddns', 'dhcp6', 'msgq',
- 'resolver', 'sockcreator', 'stats', 'xfrin', 'xfrout',
- 'zonemgr']
- LIBEXECPATH = ':'.join(['@abs_top_builddir@/src/bin/' + p for p in
- programdirs])
- else:
- DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
- PLUGIN_PATHS = ["@prefix@/share/@PACKAGE@/config_plugins"]
- LIBEXECPATH = ("@libexecdir@/@PACKAGE@"). \
- replace("${exec_prefix}", "@exec_prefix@"). \
- replace("${prefix}", "@prefix@")
- # For testing the plugins so they can find their own spec files
- if "B10_TEST_PLUGIN_DIR" in os.environ:
- PLUGIN_PATHS = os.environ["B10_TEST_PLUGIN_DIR"].split(':')
-
-reload()
+++ /dev/null
-# The following build order must be maintained.
-SUBDIRS = util cc config dns log net testutils bind10 log_messages \
- sysinfo
-
-python_PYTHON = __init__.py
-
-pythondir = $(pyexecdir)/isc
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-"""
-This is the top directory for common BIND 10 Python modules and packages.
-"""
+++ /dev/null
-SUBDIRS = . tests
-
-python_PYTHON = __init__.py sockcreator.py component.py special_component.py \
- socket_cache.py
-pythondir = $(pyexecdir)/isc/bind10
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-Module for managing components (abstraction of process). It allows starting
-them in given order, handling when they crash (what happens depends on kind
-of component) and shutting down. It also handles the configuration of this.
-
-Dependencies between them are not yet handled. It might turn out they are
-needed, in that case they will be added sometime in future.
-
-This framework allows for a single process to be started multiple times (by
-specifying multiple components with the same configuration). We might want
-to add a more convenient support (like providing a count argument to the
-configuration). This is yet to be designed.
-"""
-
-import isc.log
-from isc.log_messages.init_messages import *
-import time
-import os
-import signal
-
-logger = isc.log.Logger("init")
-DBG_TRACE_DATA = 20
-DBG_TRACE_DETAILED = 80
-
-START_CMD = 'start'
-STOP_CMD = 'stop'
-
-STARTED_OK_TIME = 10
-COMPONENT_RESTART_DELAY = 10
-
-STATE_DEAD = 'dead'
-STATE_STOPPED = 'stopped'
-STATE_RESTARTING = 'restarting'
-STATE_RUNNING = 'running'
-
-def get_signame(signal_number):
- """Return the symbolic name for a signal."""
- for sig in dir(signal):
- if sig.startswith("SIG") and sig[3].isalnum():
- if getattr(signal, sig) == signal_number:
- return sig
- return "unknown signal"
-
-class BaseComponent:
- """
- This represents a single component. This one is an abstract base class.
- There are some methods which should be left untouched, but there are
- others which define the interface only and should be overriden in
- concrete implementations.
-
- The component is in one of the three states:
- - Stopped - it is either not started yet or it was explicitly stopped.
- The component is created in this state (it must be asked to start
- explicitly).
- - Running - after start() was called, it started successfully and is
- now running.
- - Restarting - the component failed (crashed) and is waiting for a restart
- - Dead - it failed and can not be resurrected.
-
- Init
- | stop()
- | +-----------------------+
- | | |
- v | start() success |
- Stopped --------+--------> Running <----------+
- | | |
- |failure | failed() |
- | | |
- v | | start()/restart()
- +<-----------+ |
- | |
- | kind == dispensable or kind|== needed and failed late
- +-----------------------> Restarting
- |
- | kind == core or kind == needed and it failed too soon
- v
- Dead
-
- Note that there are still situations which are not handled properly here.
- We don't recognize a component that is starting up, but not ready yet, one
- that is already shutting down, impossible to stop, etc. We need to add more
- states in future to handle it properly.
- """
- def __init__(self, b10_init, kind):
- """
- Creates the component in not running mode.
-
- The parameters are:
- - `b10_init` the b10_init object to plug into. The component needs
- to plug into it to know when it failed, etc.
- - `kind` is the kind of component. It may be one of:
- * 'core' means the system can't run without it and it can't be
- safely restarted. If it does not start, the system is brought
- down. If it crashes, the system is turned off as well (with
- non-zero exit status).
- * 'needed' means the system is able to restart the component,
- but it is vital part of the service (like auth server). If
- it fails to start or crashes in less than 10s after the first
- startup, the system is brought down. If it crashes later on,
- it is restarted (see below).
- * 'dispensable' means the component should be running, but if it
- doesn't start or crashes for some reason, the system simply tries
- to restart it and keeps running.
-
- For components that are restarted, the restarts are not always
- immediate; if the component has run for more than
- COMPONENT_RESTART_DELAY (10) seconds, they are restarted right
- away. If the component has not run that long, the system waits
- until that time has passed (since the last start) until the
- component is restarted.
-
- Note that the __init__ method of child class should have these
- parameters:
-
- __init__(self, process, b10_init, kind, address=None, params=None)
-
- The extra parameters are:
- - `process` - which program should be started.
- - `address` - the address on message bus, used to talk to the
- component.
- - `params` - parameters to the program.
-
- The methods you should not override are:
- - start
- - stop
- - failed
- - running
-
- You should override:
- - _start_internal
- - _stop_internal
- - _failed_internal (if you like, the empty default might be suitable)
- - name
- - pid
- - kill
- """
- if kind not in ['core', 'needed', 'dispensable']:
- raise ValueError('Component kind can not be ' + kind)
- self.__state = STATE_STOPPED
- self._kind = kind
- self._b10_init = b10_init
- self._original_start_time = None
-
- def start(self):
- """
- Start the component for the first time or restart it. It runs
- _start_internal to actually start the component.
-
- If you try to start an already running component, it raises ValueError.
- """
- if self.__state == STATE_DEAD:
- raise ValueError("Can't resurrect already dead component")
- if self.is_running():
- raise ValueError("Can't start already running component")
- logger.info(BIND10_COMPONENT_START, self.name())
- self.__state = STATE_RUNNING
- self.__start_time = time.time()
- if self._original_start_time is None:
- self._original_start_time = self.__start_time
- self._restart_time = None
- try:
- self._start_internal()
- except Exception as e:
- logger.error(BIND10_COMPONENT_START_EXCEPTION, self.name(), e)
- try:
- self.failed(None)
- finally:
- # Even failed() can fail if this happens during initial startup
- # time. In that case we'd rather propagate the original reason
- # for the failure than the fact that failed() failed. So we
- # always re-raise the original exception.
- raise e
-
- def stop(self):
- """
- Stop the component. It calls _stop_internal to do the actual
- stopping.
-
- If you try to stop a component that is not running, it raises
- ValueError.
- """
- # This is not tested. It talks with the outher world, which is out
- # of scope of unittests.
- if not self.is_running():
- raise ValueError("Can't stop a component which is not running")
- logger.info(BIND10_COMPONENT_STOP, self.name())
- self.__state = STATE_STOPPED
- self._stop_internal()
-
- def failed(self, exit_code):
- """
- Notify the component it crashed. This will be called from b10_init
- object.
-
- If you try to call failed on a component that is not running,
- a ValueError is raised.
-
- If it is a core component or needed component and it was started only
- recently, the component will become dead and will ask b10_init to shut
- down with error exit status. A dead component can't be started again.
-
- Otherwise the component will try to restart.
-
- The exit code is used for logging. It might be None.
-
- It calls _failed_internal internally.
-
- Returns True if the process was immediately restarted, returns
- False is the process was not restarted, either because
- it is considered a core or needed component, or because
- the component is to be restarted later.
- """
-
- if exit_code is not None:
- if os.WIFEXITED(exit_code):
- exit_str = "process exited normally with exit status %d" % (exit_code)
- elif os.WIFSIGNALED(exit_code):
- sig = os.WTERMSIG(exit_code)
- signame = get_signame(sig)
- if os.WCOREDUMP(exit_code):
- exit_str = "process dumped core with exit status %d (killed by signal %d: %s)" % (exit_code, sig, signame)
- else:
- exit_str = "process terminated with exit status %d (killed by signal %d: %s)" % (exit_code, sig, signame)
- else:
- exit_str = "unknown condition with exit status %d" % (exit_code)
- else:
- exit_str = "unknown condition"
-
- logger.error(BIND10_COMPONENT_FAILED, self.name(), self.pid(),
- exit_str)
- if not self.is_running():
- raise ValueError("Can't fail component that isn't running")
- self.__state = STATE_RESTARTING # tentatively set, maybe changed to DEAD
- self._failed_internal()
- # If it is a core component or the needed component failed to start
- # (including it stopped really soon)
- if self._kind == 'core' or \
- (self._kind == 'needed' and time.time() - STARTED_OK_TIME <
- self._original_start_time):
- self.__state = STATE_DEAD
- logger.fatal(BIND10_COMPONENT_UNSATISFIED, self.name())
- self._b10_init.component_shutdown(1)
- return False
- # This means we want to restart
- else:
- # if the component was only running for a short time, don't
- # restart right away, but set a time it wants to restarted,
- # and return that it wants to be restarted later
- self.set_restart_time()
- return self.restart()
-
- def set_restart_time(self):
- """Calculates and sets the time this component should be restarted.
- Currently, it uses a very basic algorithm; start time +
- RESTART_DELAY (10 seconds). This algorithm may be improved upon
- in the future.
- """
- self._restart_at = self.__start_time + COMPONENT_RESTART_DELAY
-
- def get_restart_time(self):
- """Returns the time at which this component should be restarted."""
- return self._restart_at
-
- def restart(self, now = None):
- """Restarts the component if it has a restart_time and if the value
- of the restart_time is smaller than 'now'.
-
- If the parameter 'now' is given, its value will be used instead
- of calling time.time().
-
- Returns True if the component is restarted, False if not."""
- if now is None:
- now = time.time()
- if self.get_restart_time() is not None and\
- self.get_restart_time() < now:
- self.start()
- return True
- else:
- return False
-
- def is_running(self):
- """
- Informs if the component is currently running. It assumes the failed
- is called whenever the component really fails and there might be some
- time in between actual failure and the call, so this might be
- inaccurate (it corresponds to the thing the object thinks is true, not
- to the real "external" state).
-
- It is not expected for this method to be overriden.
- """
- return self.__state == STATE_RUNNING
-
- def is_restarting(self):
- """Informs if the component has failed and is waiting for a restart.
-
- Unlike the case of is_running(), if this returns True it always means
- the corresponding process has died and not yet restarted.
-
- """
- return self.__state == STATE_RESTARTING
-
- def _start_internal(self):
- """
- This method does the actual starting of a process. You need to override
- this method to do the actual starting.
-
- The ability to override this method presents some flexibility. It
- allows processes started in a strange way, as well as components that
- have no processes at all or components with multiple processes (in case
- of multiple processes, care should be taken to make their
- started/stopped state in sync and all the processes that can fail
- should be registered).
-
- You should register all the processes created by calling
- self._b10_init.register_process.
- """
- pass
-
- def _stop_internal(self):
- """
- This is the method that does the actual stopping of a component.
- You need to provide it in a concrete implementation.
-
- Also, note that it is a bad idea to raise exceptions from here.
- Under such circumstance, the component will be considered stopped,
- and the exception propagated, but we can't be sure it really is
- dead.
- """
- pass
-
- def _failed_internal(self):
- """
- This method is called from failed. You can replace it if you need
- some specific behaviour when the component crashes. The default
- implementation is empty.
-
- Do not raise exceptions from here, please. The propper shutdown
- would have not happened.
- """
- pass
-
- def name(self):
- """
- Provides human readable name of the component, for logging and similar
- purposes.
-
- You need to provide this method in a concrete implementation.
- """
- pass
-
- def pid(self):
- """
- Provides a PID of a process, if the component is real running process.
- This may return None in cases when there's no process involved with the
- component or in case the component is not started yet.
-
- However, it is expected the component preserves the pid after it was
- stopped, to ensure we can log it when we ask it to be killed (in case
- the process refused to stop willingly).
-
- You need to provide this method in a concrete implementation.
- """
- pass
-
- def kill(self, forceful=False):
- """
- Kills the component.
-
- If forcefull is true, it should do it in more direct and aggressive way
- (for example by using SIGKILL or some equivalent). If it is false, more
- peaceful way should be used (SIGTERM or equivalent).
-
- You need to provide this method in a concrete implementation.
- """
- pass
-
- def address(self):
- """
- Provides the name of the address used on the message bus
-
- You need to provide this method in a concrete implementation.
- """
- pass
-
-class Component(BaseComponent):
- """
- The most common implementation of a component. It can be used either
- directly, and it will just start the process without anything special,
- or slightly customised by passing a start_func hook to the __init__
- to change the way it starts.
-
- If such customisation isn't enough, you should inherit BaseComponent
- directly. It is not recommended to override methods of this class
- on one-by-one basis.
- """
- def __init__(self, process, b10_init, kind, address=None, params=None,
- start_func=None):
- """
- Creates the component in not running mode.
-
- The parameters are:
- - `process` is the name of the process to start.
- - `b10_init` the b10-init object to plug into. The component needs to
- plug into it to know when it failed, etc.
- - `kind` is the kind of component. Refer to the documentation of
- BaseComponent for details.
- - `address` is the address on message bus. It is used to ask it to
- shut down at the end. If you specialize the class for a component
- that is shut down differently, it might be None.
- - `params` is a list of parameters to pass to the process when it
- starts. It is currently unused and this support is left out for
- now.
- - `start_func` is a function called when it is started. It is supposed
- to start up the process and return a ProcInfo object describing it.
- There's a sensible default if not provided, which just launches
- the program without any special care.
- """
- BaseComponent.__init__(self, b10_init, kind)
- self._process = process
- self._start_func = start_func
- self._address = address
- self._params = params
- self._procinfo = None
-
- def _start_internal(self):
- """
- You can change the "core" of this function by setting self._start_func
- to a function without parameters. Such function should start the
- process and return the procinfo object describing the running process.
-
- If you don't provide the _start_func, the usual startup by calling
- b10_init.start_simple is performed.
- """
- # This one is not tested. For one, it starts a real process
- # which is out of scope of unit tests, for another, it just
- # delegates the starting to other function in b10_init (if a derived
- # class does not provide an override function), which is tested
- # by use.
- if self._start_func is not None:
- procinfo = self._start_func()
- else:
- # TODO Handle params, etc
- procinfo = self._b10_init.start_simple(self._process)
- self._procinfo = procinfo
- self._b10_init.register_process(self.pid(), self)
-
- def _stop_internal(self):
- self._b10_init.stop_process(self._process, self._address, self.pid())
- # TODO Some way to wait for the process that doesn't want to
- # terminate and kill it would prove nice (or add it to b10_init
- # somewhere?)
-
- def name(self):
- """
- Returns the name, derived from the process name.
- """
- return self._process
-
- def pid(self):
- return self._procinfo.pid if self._procinfo is not None else None
-
- def kill(self, forcefull=False):
- if self._procinfo is not None:
- if forcefull:
- self._procinfo.process.kill()
- else:
- self._procinfo.process.terminate()
-
- def address(self):
- """
- Returns the name of the address used on the message bus
- """
- return self._address
-
-class Configurator:
- """
- This thing keeps track of configuration changes and starts and stops
- components as it goes. It also handles the initial startup and final
- shutdown.
-
- Note that this will allow you to stop (by invoking reconfigure) a core
- component. There should be some kind of layer protecting users from ever
- doing so (users must not stop the config manager, message queue and stuff
- like that or the system won't start again). However, if a user specifies
- b10-auth as core, it is safe to stop that one.
-
- The parameters are:
- * `b10_init`: The b10-init we are managing for.
- * `specials`: Dict of specially started components. Each item is a class
- representing the component.
-
- The configuration passed to it (by startup() and reconfigure()) is a
- dictionary, each item represents one component that should be running.
- The key is an unique identifier used to reference the component. The
- value is a dictionary describing the component. All items in the
- description is optional unless told otherwise and they are as follows:
- * `special` - Some components are started in a special way. If it is
- present, it specifies which class from the specials parameter should
- be used to create the component. In that case, some of the following
- items might be irrelevant, depending on the special component chosen.
- If it is not there, the basic Component class is used.
- * `process` - Name of the executable to start. If it is not present,
- it defaults to the identifier of the component.
- * `kind` - The kind of component, either of 'core', 'needed' and
- 'dispensable'. This specifies what happens if the component fails.
- This one is required.
- * `address` - The address of the component on message bus. It is used
- to shut down the component. All special components currently either
- know their own address or don't need one and ignore it. The common
- components should provide this.
- * `params` - The command line parameters of the executable. Defaults
- to no parameters. It is currently unused.
- * `priority` - When starting the component, the components with higher
- priority are started before the ones with lower priority. If it is
- not present, it defaults to 0.
- """
- def __init__(self, b10_init, specials = {}):
- """
- Initializes the configurator, but nothing is started yet.
-
- The b10_init parameter is the b10-init object used to start and stop
- processes.
- """
- self.__b10_init = b10_init
- # These could be __private, but as we access them from within unittest,
- # it's more comfortable to have them just _protected.
-
- # They are tuples (configuration, component)
- self._components = {}
- self._running = False
- self.__specials = specials
-
- def __reconfigure_internal(self, old, new):
- """
- Does a switch from one configuration to another.
- """
- self._run_plan(self._build_plan(old, new))
-
- def startup(self, configuration):
- """
- Starts the first set of processes. This configuration is expected
- to be hardcoded from the b10-init itself to start the configuration
- manager and other similar things.
- """
- if self._running:
- raise ValueError("Trying to start the component configurator " +
- "twice")
- logger.info(BIND10_CONFIGURATOR_START)
- self.__reconfigure_internal(self._components, configuration)
- self._running = True
-
- def shutdown(self):
- """
- Shuts everything down.
-
- It is not expected that anyone would want to shutdown and then start
- the configurator again, so we don't explicitly make sure that would
- work. However, we are not aware of anything that would make it not
- work either.
- """
- if not self._running:
- raise ValueError("Trying to shutdown the component " +
- "configurator while it's not yet running")
- logger.info(BIND10_CONFIGURATOR_STOP)
- self._running = False
- self.__reconfigure_internal(self._components, {})
-
- def has_component(self, component):
- '''Return if a specified component is configured.'''
- # Values of self._components are tuples of (config, component).
- # Extract the components of the tuples and see if the given one
- # is included.
- return component in map(lambda x: x[1], self._components.values())
-
- def reconfigure(self, configuration):
- """
- Changes configuration from the current one to the provided. It
- starts and stops all the components as needed (eg. if there's
- a component that was not in the original configuration, it is
- started, any component that was in the old and is not in the
- new one is stopped).
- """
- if not self._running:
- raise ValueError("Trying to reconfigure the component " +
- "configurator while it's not yet running")
- logger.info(BIND10_CONFIGURATOR_RECONFIGURE)
- self.__reconfigure_internal(self._components, configuration)
-
- def _build_plan(self, old, new):
- """
- Builds a plan how to transfer from the old configuration to the new
- one. It'll be sorted by priority and it will contain the components
- (already created, but not started). Each command in the plan is a dict,
- so it can be extended any time in future to include whatever
- parameters each operation might need.
-
- Any configuration problems are expected to be handled here, so the
- plan is not yet run.
- """
- logger.debug(DBG_TRACE_DATA, BIND10_CONFIGURATOR_BUILD, old, new)
- plan = []
- # Handle removals of old components
- for cname in old.keys():
- if cname not in new:
- component = self._components[cname][1]
- if component.is_running() or component.is_restarting():
- plan.append({
- 'command': STOP_CMD,
- 'component': component,
- 'name': cname
- })
- # Handle transitions of configuration of what is here
- for cname in new.keys():
- if cname in old:
- for option in ['special', 'process', 'kind', 'address',
- 'params']:
- if new[cname].get(option) != old[cname][0].get(option):
- raise NotImplementedError('Changing configuration of' +
- ' a running component is ' +
- 'not yet supported. Remove' +
- ' and re-add ' + cname +
- ' to get the same effect')
- # Handle introduction of new components
- plan_add = []
- for cname in new.keys():
- if cname not in old:
- component_config = new[cname]
- creator = Component
- if 'special' in component_config:
- # TODO: Better error handling
- creator = self.__specials[component_config['special']]
- component = creator(component_config.get('process', cname),
- self.__b10_init, component_config['kind'],
- component_config.get('address'),
- component_config.get('params'))
- priority = component_config.get('priority', 0)
- # We store tuples, priority first, so we can easily sort
- plan_add.append((priority, {
- 'component': component,
- 'command': START_CMD,
- 'name': cname,
- 'config': component_config
- }))
- # Push the starts there sorted by priority
- plan.extend([command for (_, command) in sorted(plan_add,
- reverse=True,
- key=lambda command:
- command[0])])
- return plan
-
- def running(self):
- """
- Returns if the configurator is running (eg. was started by startup and
- not yet stopped by shutdown).
- """
- return self._running
-
- def _run_plan(self, plan):
- """
- Run a plan, created beforehand by _build_plan.
-
- With the start and stop commands, it also adds and removes components
- in _components.
-
- Currently implemented commands are:
- * start
- * stop
-
- The plan is a list of tasks, each task is a dictionary. It must contain
- at last 'component' (a component object to work with) and 'command'
- (the command to do). Currently, both existing commands need 'name' of
- the component as well (the identifier from configuration). The 'start'
- one needs the 'config' to be there, which is the configuration description
- of the component.
- """
- done = 0
- try:
- logger.debug(DBG_TRACE_DATA, BIND10_CONFIGURATOR_RUN, len(plan))
- for task in plan:
- component = task['component']
- command = task['command']
- logger.debug(DBG_TRACE_DETAILED, BIND10_CONFIGURATOR_TASK,
- command, component.name())
- if command == START_CMD:
- component.start()
- self._components[task['name']] = (task['config'],
- component)
- elif command == STOP_CMD:
- if component.is_running():
- component.stop()
- del self._components[task['name']]
- else:
- # Can Not Happen (as the plans are generated by ourselves).
- # Therefore not tested.
- raise NotImplementedError("Command unknown: " + command)
- done += 1
- except:
- logger.error(BIND10_CONFIGURATOR_PLAN_INTERRUPTED, done, len(plan))
- raise
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import socket
-import struct
-import os
-import errno
-import copy
-import subprocess
-import copy
-from isc.log_messages.init_messages import *
-from libutil_io_python import recv_fd
-
-logger = isc.log.Logger("init")
-
-"""
-Module that communicates with the privileged socket creator (b10-sockcreator).
-"""
-
-class CreatorError(Exception):
- """
- Exception for socket creator related errors.
-
- It has two members: fatal and errno and they are just holding the values
- passed to the __init__ function.
- """
-
- def __init__(self, message, fatal, error_num=None):
- """
- Creates the exception. The message argument is the usual string.
- The fatal one tells if the error is fatal (eg. the creator crashed)
- and error_num is the errno value returned from socket creator, if
- applicable.
- """
- Exception.__init__(self, message)
- self.fatal = fatal
- self.errno = error_num
-
-class Parser:
- """
- This class knows the sockcreator language. It creates commands, sends them
- and receives the answers and parses them.
-
- It does not start it, the communication channel must be provided.
-
- In theory, anything here can throw a fatal CreatorError exception, but it
- happens only in case something like the creator process crashes. Any other
- occasions are mentioned explicitly.
- """
-
- def __init__(self, creator_socket):
- """
- Creates the parser. The creator_socket is socket to the socket creator
- process that will be used for communication. However, the object must
- have a read_fd() method to read the file descriptor. This slightly
- unusual trick with modifying an object is used to easy up testing.
-
- You can use WrappedSocket in production code to add the method to any
- ordinary socket.
- """
- self.__socket = creator_socket
- logger.info(BIND10_SOCKCREATOR_INIT)
-
- def terminate(self):
- """
- Asks the creator process to terminate and waits for it to close the
- socket. Does not return anything. Raises a CreatorError if there is
- still data on the socket, if there is an error closing the socket,
- or if the socket had already been closed.
- """
- if self.__socket is None:
- raise CreatorError('Terminated already', True)
- logger.info(BIND10_SOCKCREATOR_TERMINATE)
- try:
- self.__socket.sendall(b'T')
- # Wait for an EOF - it will return empty data
- eof = self.__socket.recv(1)
- if len(eof) != 0:
- raise CreatorError('Protocol error - data after terminated',
- True)
- self.__socket = None
- except socket.error as se:
- self.__socket = None
- raise CreatorError(str(se), True)
-
- def __addrport_str(self, address, port):
- '''Convert a pair of IP address and port to common form for logging.'''
- if address.family == socket.AF_INET:
- return str(address) + ':' + str(port)
- else:
- return '[' + str(address) + ']:' + str(port)
-
- def get_socket(self, address, port, socktype):
- """
- Asks the socket creator process to create a socket. Pass an address
- (the isc.net.IPaddr object), port number and socket type (either
- string "UDP", "TCP" or constant socket.SOCK_DGRAM or
- socket.SOCK_STREAM.
-
- Blocks until it is provided by the socket creator process (which
- should be fast, as it is on localhost) and returns the file descriptor
- number. It raises a CreatorError exception if the creation fails.
- """
- if self.__socket is None:
- raise CreatorError('Socket requested on terminated creator', True)
- # First, assemble the request from parts
- logger.info(BIND10_SOCKET_GET, address, port, socktype)
- data = b'S'
- if socktype == 'UDP' or socktype == socket.SOCK_DGRAM:
- data += b'U'
- elif socktype == 'TCP' or socktype == socket.SOCK_STREAM:
- data += b'T'
- else:
- raise ValueError('Unknown socket type: ' + str(socktype))
- if address.family == socket.AF_INET:
- data += b'4'
- elif address.family == socket.AF_INET6:
- data += b'6'
- else:
- raise ValueError('Unknown address family in address')
- data += struct.pack('!H', port)
- data += address.addr
- try:
- # Send the request
- self.__socket.sendall(data)
- answer = self.__socket.recv(1)
- if answer == b'S':
- # Success!
- result = self.__socket.read_fd()
- logger.info(BIND10_SOCKET_CREATED, result)
- return result
- elif answer == b'E':
- # There was an error, read the error as well
- error = self.__socket.recv(1)
- rcv_errno = struct.unpack('i',
- self.__read_all(len(struct.pack('i',
- 0))))
- if error == b'S':
- cause = 'socket'
- elif error == b'B':
- cause = 'bind'
- else:
- self.__socket = None
- logger.fatal(BIND10_SOCKCREATOR_BAD_CAUSE, error)
- raise CreatorError('Unknown error cause' + str(answer), True)
- logger.error(BIND10_SOCKET_ERROR, cause, rcv_errno[0],
- os.strerror(rcv_errno[0]))
-
- # Provide as detailed information as possible on the error,
- # as error related to socket creation is a common operation
- # trouble. In particular, we are intentionally very verbose
- # if it fails due to "permission denied" so the administrator
- # can easily identify what is wrong and how to fix it.
- addrport = self.__addrport_str(address, port)
- error_text = 'Error creating socket on ' + cause + \
- ' to be bound to ' + addrport + ': ' + \
- os.strerror(rcv_errno[0])
- if rcv_errno[0] == errno.EACCES:
- error_text += ' - probably need to restart BIND 10 ' + \
- 'as a super user'
- raise CreatorError(error_text, False, rcv_errno[0])
- else:
- self.__socket = None
- logger.fatal(BIND10_SOCKCREATOR_BAD_RESPONSE, answer)
- raise CreatorError('Unknown response ' + str(answer), True)
- except socket.error as se:
- self.__socket = None
- logger.fatal(BIND10_SOCKCREATOR_TRANSPORT_ERROR, str(se))
- raise CreatorError(str(se), True)
-
- def __read_all(self, length):
- """
- Keeps reading until length data is read or EOF or error happens.
-
- EOF is considered error as well and throws a CreatorError.
- """
- result = b''
- while len(result) < length:
- data = self.__socket.recv(length - len(result))
- if len(data) == 0:
- self.__socket = None
- logger.fatal(BIND10_SOCKCREATOR_EOF)
- raise CreatorError('Unexpected EOF', True)
- result += data
- return result
-
-class WrappedSocket:
- """
- This class wraps a socket and adds a read_fd method, so it can be used
- for the Parser class conveniently. It simply copies all its guts into
- itself and implements the method.
- """
- def __init__(self, socket):
- # Copy whatever can be copied from the socket
- for name in dir(socket):
- if name not in ['__class__', '__weakref__']:
- setattr(self, name, getattr(socket, name))
- # Keep the socket, so we can prevent it from being garbage-collected
- # and closed before we are removed ourself
- self.__orig_socket = socket
-
- def read_fd(self):
- """
- Read the file descriptor from the socket.
- """
- return recv_fd(self.fileno())
-
-# FIXME: Any idea how to test this? Starting an external process doesn't sound
-# OK
-class Creator(Parser):
- """
- This starts the socket creator and allows asking for the sockets.
-
- Note: __process shouldn't be reset once created. See the note
- of the SockCreator class for details.
- """
- def __init__(self, path):
- (local, remote) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
- # Popen does not like, for some reason, having the same socket for
- # stdin as well as stdout, so we dup it before passing it there.
- remote2 = socket.fromfd(remote.fileno(), socket.AF_UNIX,
- socket.SOCK_STREAM)
- env = copy.deepcopy(os.environ)
- env['PATH'] = path
- # We explicitly set close_fs to True; it's False by default before
- # Python 3.2. If we don't close the remaining FDs, the copy of
- # 'local' will prevent the child process from terminating when
- # the parent process died abruptly.
- self.__process = subprocess.Popen(['b10-sockcreator'], env=env,
- stdin=remote.fileno(),
- stdout=remote2.fileno(),
- close_fds=True,
- preexec_fn=self.__preexec_work)
- remote.close()
- remote2.close()
- Parser.__init__(self, WrappedSocket(local))
-
- def __preexec_work(self):
- """Function used before running a program that needs to run as a
- different user."""
- # Put us into a separate process group so we don't get
- # SIGINT signals on Ctrl-C (b10-init will shut everything down by
- # other means).
- os.setpgrp()
-
- def pid(self):
- return self.__process.pid
-
- def kill(self):
- logger.warn(BIND10_SOCKCREATOR_KILL)
- if self.__process is not None:
- self.__process.kill()
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-Here's the cache for sockets from socket creator.
-"""
-
-import os
-import random
-import isc.bind10.sockcreator
-from copy import copy
-
-class SocketError(Exception):
- """
- Exception raised when the socket creator is unable to create requested
- socket. Possible reasons might be the address it should be bound to
- is already taken, the permissions are insufficient, the address family
- is not supported on this computer and many more.
-
- The errno, if not None, is passed from the socket creator.
- """
- def __init__(self, message, errno):
- Exception.__init__(self, message)
- self.errno = errno
-
-class ShareError(Exception):
- """
- The requested socket is already taken by other component and the sharing
- parameters don't allow sharing with the new request.
- """
- pass
-
-class Socket:
- """
- This represents one socket cached by the cache program. This should never
- be used directly by a user, it is used internally by the Cache. Therefore
- many member variables are used directly instead of by a accessor method.
-
- Be warned that this object implements the __del__ method. It closes the
- socket held inside in it. But this poses various problems with garbage
- collector. In short, do not make reference cycles with this and generally
- leave this class alone to live peacefully.
- """
- def __init__(self, protocol, address, port, fileno):
- """
- Creates the socket.
-
- The protocol, address and port are preserved for the information.
- """
- self.protocol = protocol
- self.address = address
- self.port = port
- self.fileno = fileno
- # Mapping from token -> application
- self.active_tokens = {}
- # The tokens which were not yet picked up
- self.waiting_tokens = set()
- # Share modes and names by the tokens (token -> (mode, name))
- self.shares = {}
-
- def __del__(self):
- """
- Closes the file descriptor.
- """
- os.close(self.fileno)
-
- def share_compatible(self, mode, name):
- """
- Checks if the given share mode and name is compatible with the ones
- already installed here.
-
- The allowed values for mode are listed in the Cache.get_token
- function.
- """
- if mode not in ['NO', 'SAMEAPP', 'ANY']:
- raise ValueError("Mode " + mode + " is invalid")
-
- # Go through the existing ones
- for (emode, ename) in self.shares.values():
- if emode == 'NO' or mode == 'NO':
- # One of them can't live together with anything
- return False
- if (emode == 'SAMEAPP' or mode == 'SAMEAPP') and \
- ename != name:
- # One of them can't live together with someone of different
- # name
- return False
- # else both are ANY or SAMEAPP with the same name, which is OK
- # No problem found, so we consider it OK
- return True
-
-class Cache:
- """
- This is the cache for sockets from socket creator. The purpose of cache
- is to hold the sockets that were requested, until they are no longer
- needed. One reason is, the socket is created before it is sent over the
- unix domain socket in b10-init, so we need to keep it somewhere for a
- while.
-
- The other reason is, a single socket might be requested multiple times.
- So we keep it here in case someone else might ask for it.
-
- Each socket kept here has a reference count and when it drops to zero,
- it is removed from cache and closed.
-
- This is expected to be part of Init, it is not a general utility class.
-
- It is not expected to be subclassed. The methods and members are named
- as protected so tests are easier access into them.
- """
- def __init__(self, creator):
- """
- Initialization. The creator is the socket creator object
- (isc.bind10.sockcreator.Creator) which will be used to create yet
- uncached sockets.
- """
- self._creator = creator
- # The sockets we have live here, these dicts are various ways how
- # to get them. Each of them contains the Socket objects somehow
-
- # This one is dict of token: socket for the ones that were not yet
- # picked up by an application.
- self._waiting_tokens = {}
- # This format is the same as above, but for the tokens that were
- # already picked up by the application and not yet released.
- self._active_tokens = {}
- # This is a dict from applications to set of tokens used by the
- # application, for the sockets already picked up by an application
- self._active_apps = {}
- # The sockets live here to be indexed by protocol, address and
- # subsequently by port
- self._sockets = {}
- # These are just the tokens actually in use, so we don't generate
- # dupes. If one is dropped, it can be potentially reclaimed.
- self._live_tokens = set()
-
- def get_token(self, protocol, address, port, share_mode, share_name):
- """
- This requests a token representing a socket. The socket is either
- found in the cache already or requested from the creator at this time
- (and cached for later time).
-
- The parameters are:
- - protocol: either 'UDP' or 'TCP'
- - address: the IPAddr object representing the address to bind to
- - port: integer saying which port to bind to
- - share_mode: either 'NO', 'SAMEAPP' or 'ANY', specifying how the
- socket can be shared with others. See bin/bind10/creatorapi.txt
- for details.
- - share_name: the name of application, in case of 'SAMEAPP' share
- mode. Only requests with the same name can share the socket.
-
- If the call is successful, it returns a string token which can be
- used to pick up the socket later. The socket is created with reference
- count zero and if it isn't picked up soon enough (the time yet has to
- be set), it will be removed and the token is invalid.
-
- It can fail in various ways. Explicitly listed exceptions are:
- - SocketError: this one is thrown if the socket creator couldn't provide
- the socket and it is not yet cached (it belongs to other application,
- for example).
- - ShareError: the socket is already in the cache, but it can't be
- shared due to share_mode and share_name combination (both the request
- restrictions and of all copies of socket handed out are considered,
- so it can be raised even if you call it with share_mode 'ANY').
- - isc.bind10.sockcreator.CreatorError: fatal creator errors are
- propagated. Thay should cause b10-init to exit if ever encountered.
-
- Note that it isn't guaranteed the tokens would be unique and they
- should be used as an opaque handle only.
- """
- addr_str = str(address)
- try:
- socket = self._sockets[protocol][addr_str][port]
- except KeyError:
- # Something in the dicts is not there, so socket is to be
- # created
- try:
- fileno = self._creator.get_socket(address, port, protocol)
- except isc.bind10.sockcreator.CreatorError as ce:
- if ce.fatal:
- raise
- else:
- raise SocketError(str(ce), ce.errno)
- socket = Socket(protocol, address, port, fileno)
- # And cache it
- if protocol not in self._sockets:
- self._sockets[protocol] = {}
- if addr_str not in self._sockets[protocol]:
- self._sockets[protocol][addr_str] = {}
- self._sockets[protocol][addr_str][port] = socket
- # Now we get the token, check it is compatible
- if not socket.share_compatible(share_mode, share_name):
- raise ShareError("Cached socket not compatible with mode " +
- share_mode + " and name " + share_name)
- # Grab yet unused token
- token = 't' + str(random.randint(0, 2 ** 32-1))
- while token in self._live_tokens:
- token = 't' + str(random.randint(0, 2 ** 32-1))
- self._waiting_tokens[token] = socket
- self._live_tokens.add(token)
- socket.shares[token] = (share_mode, share_name)
- socket.waiting_tokens.add(token)
- return token
-
- def get_socket(self, token, application):
- """
- This returns the socket created by get_token. The token should be the
- one returned from previous call from get_token. The token can be used
- only once to receive the socket.
-
- The application is a token representing the application that
- requested it. Currently, b10-init uses the file descriptor of
- connection from the application, but anything which can be a key in
- a dict is OK from the cache's point of view. You just need to use
- the same thing in drop_application.
-
- In case the token is considered invalid (it doesn't come from the
- get_token, it was already used, the socket wasn't picked up soon
- enough, ...), it raises ValueError.
- """
- try:
- socket = self._waiting_tokens[token]
- except KeyError:
- raise ValueError("Token " + token +
- " isn't waiting to be picked up")
- del self._waiting_tokens[token]
- self._active_tokens[token] = socket
- if application not in self._active_apps:
- self._active_apps[application] = set()
- self._active_apps[application].add(token)
- socket.waiting_tokens.remove(token)
- socket.active_tokens[token] = application
- return socket.fileno
-
- def drop_socket(self, token):
- """
- This signals the application no longer uses the socket which was
- requested by the given token. It decreases the reference count for
- the socket and closes and removes the cached copy if it was the last
- one.
-
- It raises ValueError if the token doesn't exist.
- """
- try:
- socket = self._active_tokens[token]
- except KeyError:
- raise ValueError("Token " + token + " doesn't represent an " +
- "active socket")
- # Now, remove everything from the bookkeeping
- del socket.shares[token]
- app = socket.active_tokens[token]
- del socket.active_tokens[token]
- del self._active_tokens[token]
- self._active_apps[app].remove(token)
- if len(self._active_apps[app]) == 0:
- del self._active_apps[app]
- self._live_tokens.remove(token)
- # The socket is not used by anything now, so remove it
- if len(socket.active_tokens) == 0 and len(socket.waiting_tokens) == 0:
- addr = str(socket.address)
- port = socket.port
- proto = socket.protocol
- del self._sockets[proto][addr][port]
- # Clean up empty branches of the structure
- if len(self._sockets[proto][addr]) == 0:
- del self._sockets[proto][addr]
- if len(self._sockets[proto]) == 0:
- del self._sockets[proto]
-
- def drop_application(self, application):
- """
- This signals the application terminated and all sockets it picked up
- should be considered unused by it now. It effectively calls drop_socket
- on each of the sockets the application picked up and didn't drop yet.
-
- If the application is invalid (no get_socket was successful with this
- value of application), it raises ValueError.
- """
- try:
- # Get a copy. Who knows how iteration works through sets if we
- # delete from it during the time, so we'll just have our own copy
- # to iterate
- to_drop = copy(self._active_apps[application])
- except KeyError:
- raise ValueError("Application " + str(application) +
- " doesn't hold any sockets")
- for token in to_drop:
- self.drop_socket(token)
- # We don't call del now. The last drop_socket should have
- # removed the application key as well.
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-from isc.bind10.component import Component, BaseComponent
-import isc.bind10.sockcreator
-from bind10_config import LIBEXECPATH
-import os
-import isc.log
-
-class SockCreator(BaseComponent):
- """
- The socket creator component. Will start and stop the socket creator
- accordingly.
-
- Note: _creator shouldn't be reset explicitly once created. The
- underlying Popen object would then wait() the child process internally,
- which breaks the assumption of b10-init, who is expecting to see
- the process die in waitpid().
- """
- def __init__(self, process, b10_init, kind, address=None, params=None):
- BaseComponent.__init__(self, b10_init, kind)
- self.__creator = None
-
- def _start_internal(self):
- self._b10_init.curproc = 'b10-sockcreator'
- self.__creator = isc.bind10.sockcreator.Creator(LIBEXECPATH + ':' +
- os.environ['PATH'])
- self._b10_init.register_process(self.pid(), self)
- self._b10_init.set_creator(self.__creator)
- self._b10_init.log_started(self.pid())
-
- # We are now ready for switching user.
- self._b10_init.change_user()
-
- def _stop_internal(self):
- self.__creator.terminate()
-
- def name(self):
- return "Socket creator"
-
- def pid(self):
- """
- Pid of the socket creator. It is provided differently from a usual
- component.
- """
- return self.__creator.pid() if self.__creator else None
-
- def kill(self, forceful=False):
- # We don't really care about forceful here
- if self.__creator:
- self.__creator.kill()
-
-class Msgq(Component):
- """
- The message queue. Starting is passed to b10-init, stopping is not
- supported and we leave b10-init kill it by signal.
- """
- def __init__(self, process, b10_init, kind, address=None, params=None):
- Component.__init__(self, process, b10_init, kind, None, None,
- b10_init.start_msgq)
-
- def _stop_internal(self):
- """
- We can't really stop the message queue, as many processes may need
- it for their shutdown and it doesn't have a shutdown command anyway.
- But as it is stateless, it's OK to kill it.
-
- So we disable this method (as the only time it could be called is
- during shutdown) and wait for b10-init to kill it in the next shutdown
- step.
-
- This actually breaks the recommendation at Component we shouldn't
- override its methods one by one. This is a special case, because
- we don't provide a different implementation, we completely disable
- the method by providing an empty one. This can't hurt the internals.
- """
- pass
-
-class CfgMgr(Component):
- def __init__(self, process, b10_init, kind, address=None, params=None):
- Component.__init__(self, process, b10_init, kind, 'ConfigManager',
- None, b10_init.start_cfgmgr)
-
-class Auth(Component):
- def __init__(self, process, b10_init, kind, address=None, params=None):
- Component.__init__(self, process, b10_init, kind, 'Auth', None,
- b10_init.start_auth)
-
-class Resolver(Component):
- def __init__(self, process, b10_init, kind, address=None, params=None):
- Component.__init__(self, process, b10_init, kind, 'Resolver', None,
- b10_init.start_resolver)
-
-class CmdCtl(Component):
- def __init__(self, process, b10_init, kind, address=None, params=None):
- Component.__init__(self, process, b10_init, kind, 'Cmdctl', None,
- b10_init.start_cmdctl)
-def get_specials():
- """
- List of specially started components. Each one should be the class than can
- be created for that component.
- """
- return {
- 'sockcreator': SockCreator,
- 'msgq': Msgq,
- 'cfgmgr': CfgMgr,
- # TODO: Should these be replaced by configuration in config manager only?
- # They should not have any parameters anyway
- 'auth': Auth,
- 'resolver': Resolver,
- 'cmdctl': CmdCtl
- }
+++ /dev/null
-PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-#PYTESTS = args_test.py bind10_test.py
-# NOTE: this has a generated test found in the builddir
-PYTESTS =
-
-EXTRA_DIST = $(PYTESTS)
-
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
-LIBRARY_PATH_PLACEHOLDER =
-if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
-endif
-
-# test using command-line arguments, so use check-local target instead of TESTS
-check-local:
-if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
- rm -f .coverage
- ${LN_S} $(abs_top_srcdir)/.coverage .coverage
-endif
- for pytest in $(PYTESTS) ; do \
- echo Running test: $$pytest ; \
- $(LIBRARY_PATH_PLACEHOLDER) \
- PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
- B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
- BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
- $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
- done
-
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-Tests for the isc.bind10.component module and the
-isc.bind10.special_component module.
-"""
-
-import unittest
-import isc.log
-import time
-import copy
-from isc.bind10.component import Component, Configurator, BaseComponent
-import isc.bind10.special_component
-
-class TestError(Exception):
- """
- Just a private exception not known to anybody we use for our tests.
- """
- pass
-
-class InitUtils:
- """
- A class that brings some utilities for pretending we're Init.
- This is expected to be inherited by the testcases themselves.
- """
- def setUp(self):
- """
- Part of setup. Should be called by descendant's setUp.
- """
- self._shutdown = False
- self._exitcode = None
- # Back up the time function, we may want to replace it with something
- self.__orig_time = isc.bind10.component.time.time
-
- def tearDown(self):
- """
- Clean up after tests. If the descendant implements a tearDown, it
- should call this method internally.
- """
- # Return the original time function
- isc.bind10.component.time.time = self.__orig_time
-
- def component_shutdown(self, exitcode=0):
- """
- Mock function to shut down. We just note we were asked to do so.
- """
- self._shutdown = True
- self._exitcode = exitcode
-
- def _timeskip(self):
- """
- Skip in time to future some 30s. Implemented by replacing the
- time.time function in the tested module with function that returns
- current time increased by 30.
- """
- tm = time.time()
- isc.bind10.component.time.time = lambda: tm + 30
-
- # Few functions that pretend to start something. Part of pretending of
- # being b10-init.
- def start_msgq(self):
- pass
-
- def start_cfgmgr(self):
- pass
-
- def start_auth(self):
- pass
-
- def start_resolver(self):
- pass
-
- def start_cmdctl(self):
- pass
-
-class ComponentTests(InitUtils, unittest.TestCase):
- """
- Tests for the bind10.component.Component class
- """
- def setUp(self):
- """
- Pretend a newly started system.
- """
- InitUtils.setUp(self)
- self._shutdown = False
- self._exitcode = None
- self.__start_called = False
- self.__stop_called = False
- self.__failed_called = False
- self.__registered_processes = {}
- self.__stop_process_params = None
- self.__start_simple_params = None
- # Pretending to be b10-init
- self.__change_user_called = False
-
- def change_user(self):
- self.__change_user_called = True # just record the fact it's called
-
- def __start(self):
- """
- Mock function, installed into the component into _start_internal.
- This only notes the component was "started".
- """
- self.__start_called = True
-
- def __stop(self):
- """
- Mock function, installed into the component into _stop_internal.
- This only notes the component was "stopped".
- """
- self.__stop_called = True
-
- def __fail(self):
- """
- Mock function, installed into the component into _failed_internal.
- This only notes the component called the method.
- """
- self.__failed_called = True
-
- def __fail_to_start(self):
- """
- Mock function. It can be installed into the component's _start_internal
- to simulate a component that fails to start by raising an exception.
- """
- orig_started = self.__start_called
- self.__start_called = True
- if not orig_started:
- # This one is from restart. Avoid infinite recursion for now.
- # FIXME: We should use the restart scheduler to avoid it, not this.
- raise TestError("Test error")
-
- def __create_component(self, kind):
- """
- Convenience function that creates a component of given kind
- and installs the mock functions into it so we can hook up into
- its behaviour.
-
- The process used is some nonsense, as this isn't used in this
- kind of tests and we pretend to be the b10-init.
- """
- component = Component('No process', self, kind, 'homeless', [])
- component._start_internal = self.__start
- component._stop_internal = self.__stop
- component._failed_internal = self.__fail
- return component
-
- def test_name(self):
- """
- Test the name provides whatever we passed to the constructor as process.
- """
- component = self.__create_component('core')
- self.assertEqual('No process', component.name())
-
- def test_address(self):
- """
- Test the address provides whatever we passed to the constructor as process.
- """
- component = self.__create_component('core')
- self.assertEqual("homeless", component.address())
-
- def test_guts(self):
- """
- Test the correct data are stored inside the component.
- """
- component = self.__create_component('core')
- self.assertEqual(self, component._b10_init)
- self.assertEqual("No process", component._process)
- self.assertEqual(None, component._start_func)
- self.assertEqual("homeless", component._address)
- self.assertEqual([], component._params)
-
- def __check_startup(self, component):
- """
- Check that nothing was called yet. A newly created component should
- not get started right away, so this should pass after the creation.
- """
- self.assertFalse(self._shutdown)
- self.assertFalse(self.__start_called)
- self.assertFalse(self.__stop_called)
- self.assertFalse(self.__failed_called)
- self.assertFalse(component.is_running())
- self.assertFalse(component.is_restarting())
- # We can't stop or fail the component yet
- self.assertRaises(ValueError, component.stop)
- self.assertRaises(ValueError, component.failed, 1)
-
- def __check_started(self, component):
- """
- Check the component was started, but not stopped anyhow yet.
- """
- self.assertFalse(self._shutdown)
- self.assertTrue(self.__start_called)
- self.assertFalse(self.__stop_called)
- self.assertFalse(self.__failed_called)
- self.assertTrue(component.is_running())
- self.assertFalse(component.is_restarting())
-
- def __check_dead(self, component):
- """
- Check the component is completely dead, and the server too.
- """
- self.assertTrue(self._shutdown)
- self.assertTrue(self.__start_called)
- self.assertFalse(self.__stop_called)
- self.assertTrue(self.__failed_called)
- self.assertEqual(1, self._exitcode)
- self.assertFalse(component.is_running())
- self.assertFalse(component.is_restarting())
- # Surely it can't be stopped when already dead
- self.assertRaises(ValueError, component.stop)
- # Nor started
- self.assertRaises(ValueError, component.start)
- # Nor it can fail again
- self.assertRaises(ValueError, component.failed, 1)
-
- def __check_restarted(self, component):
- """
- Check the component restarted successfully.
-
- Reset the self.__start_called to False before calling the function when
- the component should fail.
- """
- self.assertFalse(self._shutdown)
- self.assertTrue(self.__start_called)
- self.assertFalse(self.__stop_called)
- self.assertTrue(self.__failed_called)
- self.assertTrue(component.is_running())
- self.assertFalse(component.is_restarting())
- # Check it can't be started again
- self.assertRaises(ValueError, component.start)
-
- def __check_not_restarted(self, component):
- """
- Check the component has not (yet) restarted successfully.
- """
- self.assertFalse(self._shutdown)
- self.assertTrue(self.__start_called)
- self.assertFalse(self.__stop_called)
- self.assertTrue(self.__failed_called)
- self.assertFalse(component.is_running())
- self.assertTrue(component.is_restarting())
-
- def __do_start_stop(self, kind):
- """
- This is a body of a test. It creates a component of given kind,
- then starts it and stops it. It checks correct functions are called
- and the component's status is correct.
-
- It also checks the component can't be started/stopped twice.
- """
- # Create it and check it did not do any funny stuff yet
- component = self.__create_component(kind)
- self.__check_startup(component)
- # Start it and check it called the correct starting functions
- component.start()
- self.__check_started(component)
- # Check it can't be started twice
- self.assertRaises(ValueError, component.start)
- # Stop it again and check
- component.stop()
- self.assertFalse(self._shutdown)
- self.assertTrue(self.__start_called)
- self.assertTrue(self.__stop_called)
- self.assertFalse(self.__failed_called)
- self.assertFalse(component.is_running())
- self.assertFalse(component.is_restarting())
- # Check it can't be stopped twice
- self.assertRaises(ValueError, component.stop)
- # Or failed
- self.assertRaises(ValueError, component.failed, 1)
- # But it can be started again if it is stopped
- # (no more checking here, just it doesn't crash)
- component.start()
-
- def test_start_stop_core(self):
- """
- A start-stop test for core component. See do_start_stop.
- """
- self.__do_start_stop('core')
-
- def test_start_stop_needed(self):
- """
- A start-stop test for needed component. See do_start_stop.
- """
- self.__do_start_stop('needed')
-
- def test_start_stop_dispensable(self):
- """
- A start-stop test for dispensable component. See do_start_stop.
- """
- self.__do_start_stop('dispensable')
-
- def test_start_fail_core(self):
- """
- Start and then fail a core component. It should stop the whole server.
- """
- # Just ordinary startup
- component = self.__create_component('core')
- self.__check_startup(component)
- component.start()
- self.__check_started(component)
- # Pretend the component died
- restarted = component.failed(1)
- # Since it is a core component, it should not be restarted
- self.assertFalse(restarted)
- # It should bring down the whole server
- self.__check_dead(component)
-
- def test_start_fail_core_later(self):
- """
- Start and then fail a core component, but let it be running for longer time.
- It should still stop the whole server.
- """
- # Just ordinary startup
- component = self.__create_component('core')
- self.__check_startup(component)
- component.start()
- self.__check_started(component)
- self._timeskip()
- # Pretend the component died some time later
- restarted = component.failed(1)
- # Should not be restarted
- self.assertFalse(restarted)
- # Check the component is still dead
- self.__check_dead(component)
-
- def test_start_fail_needed(self):
- """
- Start and then fail a needed component. As this happens really soon after
- being started, it is considered failure to start and should bring down the
- whole server.
- """
- # Just ordinary startup
- component = self.__create_component('needed')
- self.__check_startup(component)
- component.start()
- self.__check_started(component)
- # Make it fail right away.
- restarted = component.failed(1)
- # Should not have restarted
- self.assertFalse(restarted)
- self.__check_dead(component)
-
- def test_start_fail_needed_later(self):
- """
- Start and then fail a needed component. But the failure is later on, so
- we just restart it and will be happy.
- """
- # Just ordinary startup
- component = self.__create_component('needed')
- self.__check_startup(component)
- component.start()
- self.__check_started(component)
- # Make it fail later on
- self.__start_called = False
- self._timeskip()
- restarted = component.failed(1)
- # Should have restarted
- self.assertTrue(restarted)
- self.__check_restarted(component)
-
- def test_start_fail_dispensable(self):
- """
- Start and then fail a dispensable component. Should not get restarted.
- """
- # Just ordinary startup
- component = self.__create_component('dispensable')
- self.__check_startup(component)
- component.start()
- self.__check_started(component)
- # Make it fail right away
- restarted = component.failed(1)
- # Should signal that it did not restart
- self.assertFalse(restarted)
- self.__check_not_restarted(component)
-
- def test_start_fail_dispensable_later(self):
- """
- Start and then later on fail a dispensable component. Should just get
- restarted.
- """
- # Just ordinary startup
- component = self.__create_component('dispensable')
- self.__check_startup(component)
- component.start()
- self.__check_started(component)
- # Make it fail later on
- self._timeskip()
- restarted = component.failed(1)
- # should signal that it restarted
- self.assertTrue(restarted)
- # and check if it really did
- self.__check_restarted(component)
-
- def test_start_fail_dispensable_restart_later(self):
- """
- Start and then fail a dispensable component, wait a bit and try to
- restart. Should get restarted after the wait.
- """
- # Just ordinary startup
- component = self.__create_component('dispensable')
- self.__check_startup(component)
- component.start()
- self.__check_started(component)
- # Make it fail immediately
- restarted = component.failed(1)
- # should signal that it did not restart
- self.assertFalse(restarted)
- self.__check_not_restarted(component)
- self._timeskip()
- # try to restart again
- restarted = component.restart()
- # should signal that it restarted
- self.assertTrue(restarted)
- # and check if it really did
- self.__check_restarted(component)
-
- def test_fail_core(self):
- """
- Failure to start a core component. Should bring the system down
- and the exception should get through.
- """
- component = self.__create_component('core')
- self.__check_startup(component)
- component._start_internal = self.__fail_to_start
- self.assertRaises(TestError, component.start)
- self.__check_dead(component)
-
- def test_fail_needed(self):
- """
- Failure to start a needed component. Should bring the system down
- and the exception should get through.
- """
- component = self.__create_component('needed')
- self.__check_startup(component)
- component._start_internal = self.__fail_to_start
- self.assertRaises(TestError, component.start)
- self.__check_dead(component)
-
- def test_fail_dispensable(self):
- """
- Failure to start a dispensable component. The exception should get
- through, but it should be restarted after a time skip.
- """
- component = self.__create_component('dispensable')
- self.__check_startup(component)
- component._start_internal = self.__fail_to_start
- self.assertRaises(TestError, component.start)
- # tell it to see if it must restart
- restarted = component.restart()
- # should not have restarted yet
- self.assertFalse(restarted)
- self.__check_not_restarted(component)
- self._timeskip()
- # tell it to see if it must restart and do so, with our vision of time
- restarted = component.restart()
- # should have restarted now
- self.assertTrue(restarted)
- self.__check_restarted(component)
-
- def test_component_start_time(self):
- """
- Check that original start time is set initially, and remains the same
- after a restart, while the internal __start_time does change
- """
- # Just ordinary startup
- component = self.__create_component('dispensable')
- self.__check_startup(component)
- self.assertIsNone(component._original_start_time)
- component.start()
- self.__check_started(component)
-
- self.assertIsNotNone(component._original_start_time)
- self.assertIsNotNone(component._BaseComponent__start_time)
- original_start_time = component._original_start_time
- start_time = component._BaseComponent__start_time
- # Not restarted yet, so they should be the same
- self.assertEqual(original_start_time, start_time)
-
- self._timeskip()
- # Make it fail
- restarted = component.failed(1)
- # should signal that it restarted
- self.assertTrue(restarted)
- # and check if it really did
- self.__check_restarted(component)
-
- # original start time should not have changed
- self.assertEqual(original_start_time, component._original_start_time)
- # but actual start time should
- self.assertNotEqual(start_time, component._BaseComponent__start_time)
-
- def test_bad_kind(self):
- """
- Test the component rejects nonsensical kinds. This includes bad
- capitalization.
- """
- for kind in ['Core', 'CORE', 'nonsense', 'need ed', 'required']:
- self.assertRaises(ValueError, Component, 'No process', self, kind)
-
- def test_pid_not_running(self):
- """
- Test that a component that is not yet started doesn't have a PID.
- But it won't fail if asked for and return None.
- """
- for component_type in [Component,
- isc.bind10.special_component.SockCreator,
- isc.bind10.special_component.Msgq,
- isc.bind10.special_component.CfgMgr,
- isc.bind10.special_component.Auth,
- isc.bind10.special_component.Resolver,
- isc.bind10.special_component.CmdCtl]:
- component = component_type('none', self, 'needed')
- self.assertIsNone(component.pid())
-
- def test_kill_unstarted(self):
- """
- Try to kill the component if it's not started. Should not fail.
-
- We do not try to kill a running component, as we should not start
- it during unit tests.
- """
- component = Component('component', self, 'needed')
- component.kill()
- component.kill(True)
-
- def register_process(self, pid, process):
- """
- Part of pretending to be a b10-init
- """
- self.__registered_processes[pid] = process
-
- def test_component_attributes(self):
- """
- Test the default attributes of Component (not BaseComponent) and
- some of the methods we might be allowed to call.
- """
- class TestProcInfo:
- def __init__(self):
- self.pid = 42
- component = Component('component', self, 'needed', 'Address',
- ['hello'], TestProcInfo)
- self.assertEqual('component', component._process)
- self.assertEqual('component', component.name())
- self.assertIsNone(component._procinfo)
- self.assertIsNone(component.pid())
- self.assertEqual(['hello'], component._params)
- self.assertEqual('Address', component._address)
- self.assertFalse(component.is_running())
- self.assertEqual({}, self.__registered_processes)
- component.start()
- self.assertTrue(component.is_running())
- # Some versions of unittest miss assertIsInstance
- self.assertTrue(isinstance(component._procinfo, TestProcInfo))
- self.assertEqual(42, component.pid())
- self.assertEqual(component, self.__registered_processes.get(42))
-
- def stop_process(self, process, address, pid):
- """
- Part of pretending to be b10-init.
- """
- self.__stop_process_params = (process, address, pid)
-
- def start_simple(self, process):
- """
- Part of pretending to be b10-init.
- """
- self.__start_simple_params = process
-
- def test_component_start_stop_internal(self):
- """
- Test the behavior of _stop_internal and _start_internal.
- """
- component = Component('component', self, 'needed', 'Address')
- component.start()
- self.assertTrue(component.is_running())
- self.assertEqual('component', self.__start_simple_params)
- component.pid = lambda: 42
- component.stop()
- self.assertFalse(component.is_running())
- self.assertEqual(('component', 'Address', 42),
- self.__stop_process_params)
-
- def test_component_kill(self):
- """
- Check the kill is propagated. The case when component wasn't started
- yet is already tested elsewhere.
- """
- class Process:
- def __init__(self):
- self.killed = False
- self.terminated = False
- def kill(self):
- self.killed = True
- def terminate(self):
- self.terminated = True
- process = Process()
- class ProcInfo:
- def __init__(self):
- self.process = process
- self.pid = 42
- component = Component('component', self, 'needed', 'Address',
- [], ProcInfo)
- component.start()
- self.assertTrue(component.is_running())
- component.kill()
- self.assertTrue(process.terminated)
- self.assertFalse(process.killed)
- process.terminated = False
- component.kill(True)
- self.assertTrue(process.killed)
- self.assertFalse(process.terminated)
-
- class FakeCreator:
- def pid(self):
- return 42
- def terminate(self): pass
- def kill(self): pass
-
- def set_creator(self, creator):
- """
- Part of faking being the b10-init. Check the creator (faked as well)
- is passed here.
- """
- self.assertTrue(isinstance(creator, self.FakeCreator))
-
- def log_started(self, pid):
- """
- Part of faking the b10-init. Check the pid is the one of the fake creator.
- """
- self.assertEqual(42, pid)
-
- def test_creator(self):
- """
- Some tests around the SockCreator component.
- """
- component = isc.bind10.special_component.SockCreator(None, self,
- 'needed', None)
- orig_creator = \
- isc.bind10.special_component.isc.bind10.sockcreator.Creator
- # Just ignore the creator call
- isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
- lambda path: self.FakeCreator()
- component.start()
- self.assertTrue(self.__change_user_called)
- # Doesn't do anything, but doesn't crash
- component.stop()
- component.kill()
- component.kill(True)
- component = isc.bind10.special_component.SockCreator(None, self,
- 'needed', None)
-
-class TestComponent(BaseComponent):
- """
- A test component. It does not start any processes or so, it just logs
- information about what happens.
- """
- def __init__(self, owner, name, kind, address=None, params=None):
- """
- Initializes the component. The owner is the test that started the
- component. The logging will happen into it.
-
- The process is used as a name for the logging.
- """
- BaseComponent.__init__(self, owner, kind)
- self.__owner = owner
- self.__name = name
- self.log('init')
- self.log(kind)
- self._address = address
- self._params = params
-
- def log(self, event):
- """
- Log an event into the owner. The owner can then check the correct
- order of events that happened.
- """
- self.__owner.log.append((self.__name, event))
-
- def _start_internal(self):
- self.log('start')
-
- def _stop_internal(self):
- self.log('stop')
-
- def _failed_internal(self):
- self.log('failed')
-
- def kill(self, forceful=False):
- self.log('killed')
-
-class FailComponent(BaseComponent):
- """
- A mock component that fails whenever it is started.
- """
- def __init__(self, name, b10_init, kind, address=None, params=None):
- BaseComponent.__init__(self, b10_init, kind)
-
- def _start_internal(self):
- raise TestError("test error")
-
-class ConfiguratorTest(InitUtils, unittest.TestCase):
- """
- Tests for the configurator.
- """
- def setUp(self):
- """
- Prepare some test data for the tests.
- """
- InitUtils.setUp(self)
- self.log = []
- # The core "hardcoded" configuration
- self.__core = {
- 'core1': {
- 'priority': 5,
- 'process': 'core1',
- 'special': 'test',
- 'kind': 'core'
- },
- 'core2': {
- 'process': 'core2',
- 'special': 'test',
- 'kind': 'core'
- },
- 'core3': {
- 'process': 'core3',
- 'priority': 3,
- 'special': 'test',
- 'kind': 'core'
- }
- }
- # How they should be started. They are created in the order they are
- # found in the dict, but then they should be started by priority.
- # This expects that the same dict returns its keys in the same order
- # every time
- self.__core_log_create = []
- for core in self.__core.keys():
- self.__core_log_create.append((core, 'init'))
- self.__core_log_create.append((core, 'core'))
- self.__core_log_start = [('core1', 'start'), ('core3', 'start'),
- ('core2', 'start')]
- self.__core_log = self.__core_log_create + self.__core_log_start
- self.__specials = { 'test': self.__component_test }
-
- def __component_test(self, process, b10_init, kind, address=None,
- params=None):
- """
- Create a test component. It will log events to us.
- """
- self.assertEqual(self, b10_init)
- return TestComponent(self, process, kind, address, params)
-
- def test_init(self):
- """
- Tests the configurator can be created and it does not create
- any components yet, nor does it remember anything.
- """
- configurator = Configurator(self, self.__specials)
- self.assertEqual([], self.log)
- self.assertEqual({}, configurator._components)
- self.assertFalse(configurator.running())
-
- def test_run_plan(self):
- """
- Test the internal function of running plans. Just see it can handle
- the commands in the given order. We see that by the log.
-
- Also includes one that raises, so we see it just stops there.
- """
- # Prepare the configurator and the plan
- configurator = Configurator(self, self.__specials)
- started = self.__component_test('second', self, 'dispensable')
- started.start()
- stopped = self.__component_test('first', self, 'core')
- configurator._components = {'second': started}
- plan = [
- {
- 'component': stopped,
- 'command': 'start',
- 'name': 'first',
- 'config': {'a': 1}
- },
- {
- 'component': started,
- 'command': 'stop',
- 'name': 'second',
- 'config': {}
- },
- {
- 'component': FailComponent('third', self, 'needed'),
- 'command': 'start',
- 'name': 'third',
- 'config': {}
- },
- {
- 'component': self.__component_test('fourth', self, 'core'),
- 'command': 'start',
- 'name': 'fourth',
- 'config': {}
- }
- ]
- # Don't include the preparation into the log
- self.log = []
- # The error from the third component is propagated
- self.assertRaises(TestError, configurator._run_plan, plan)
- # The first two were handled, the rest not, due to the exception
- self.assertEqual([('first', 'start'), ('second', 'stop')], self.log)
- self.assertEqual({'first': ({'a': 1}, stopped)},
- configurator._components)
-
- def __build_components(self, config):
- """
- Insert the components into the configuration to specify possible
- Configurator._components.
-
- Actually, the components are None, but we need something to be there.
- """
- result = {}
- for name in config.keys():
- result[name] = (config[name], None)
- return result
-
- def test_build_plan(self):
- """
- Test building the plan correctly. Not complete yet, this grows as we
- add more ways of changing the plan.
- """
- configurator = Configurator(self, self.__specials)
- plan = configurator._build_plan({}, self.__core)
- # This should have created the components
- self.assertEqual(self.__core_log_create, self.log)
- self.assertEqual(3, len(plan))
- for (task, name) in zip(plan, ['core1', 'core3', 'core2']):
- self.assertTrue('component' in task)
- self.assertEqual('start', task['command'])
- self.assertEqual(name, task['name'])
- component = task['component']
- self.assertIsNone(component._address)
- self.assertIsNone(component._params)
-
- # A plan to go from older state to newer one containing more components
- bigger = copy.copy(self.__core)
- bigger['additional'] = {
- 'priority': 6,
- 'special': 'test',
- 'process': 'additional',
- 'kind': 'dispensable' # need to be dispensable so it could restart
- }
- self.log = []
- plan = configurator._build_plan(self.__build_components(self.__core),
- bigger)
- self.assertEqual([('additional', 'init'),
- ('additional', 'dispensable')],
- self.log)
- self.assertEqual(1, len(plan))
- self.assertTrue('component' in plan[0])
- component = plan[0]['component']
- self.assertEqual('start', plan[0]['command'])
- self.assertEqual('additional', plan[0]['name'])
-
- # Now remove the one component again
- # We run the plan so the component is wired into internal structures
- configurator._run_plan(plan)
- # We should have the 'additional' component in the configurator.
- self.assertTrue(configurator.has_component(component))
- for count in [0, 1]: # repeat two times, see below
- self.log = []
- plan = configurator._build_plan(self.__build_components(bigger),
- self.__core)
- self.assertEqual([], self.log)
- self.assertEqual([{
- 'command': 'stop',
- 'name': 'additional',
- 'component': component
- }], plan)
-
- if count is 0:
- # We then emulate unexpected failure of the component (but
- # before it restarts). It shouldn't confuse the
- # configurator in the second phase of the test
- component.failed(0)
- # Run the plan, confirm the specified component is gone.
- configurator._run_plan(plan)
- self.assertFalse(configurator.has_component(component))
- # There shouldn't be any other object that has the same name
- self.assertFalse('additional' in configurator._components)
-
- # We want to switch a component. So, prepare the configurator so it
- # holds one
- configurator._run_plan(configurator._build_plan(
- self.__build_components(self.__core), bigger))
- # Get a different configuration with a different component
- different = copy.copy(self.__core)
- different['another'] = {
- 'special': 'test',
- 'process': 'another',
- 'kind': 'dispensable'
- }
- self.log = []
- plan = configurator._build_plan(self.__build_components(bigger),
- different)
- self.assertEqual([('another', 'init'), ('another', 'dispensable')],
- self.log)
- self.assertEqual(2, len(plan))
- self.assertEqual('stop', plan[0]['command'])
- self.assertEqual('additional', plan[0]['name'])
- self.assertTrue('component' in plan[0])
- self.assertEqual('start', plan[1]['command'])
- self.assertEqual('another', plan[1]['name'])
- self.assertTrue('component' in plan[1])
-
- # Some slightly insane plans, like missing process, having parameters,
- # no special, etc
- plan = configurator._build_plan({}, {
- 'component': {
- 'kind': 'needed',
- 'params': ["1", "2"],
- 'address': 'address'
- }
- })
- self.assertEqual(1, len(plan))
- self.assertEqual('start', plan[0]['command'])
- self.assertEqual('component', plan[0]['name'])
- component = plan[0]['component']
- self.assertEqual('component', component.name())
- self.assertEqual(["1", "2"], component._params)
- self.assertEqual('address', component._address)
- self.assertEqual('needed', component._kind)
- # We don't use isinstance on purpose, it would allow a descendant
- self.assertTrue(type(component) is Component)
- plan = configurator._build_plan({}, {
- 'component': { 'kind': 'dispensable' }
- })
- self.assertEqual(1, len(plan))
- self.assertEqual('start', plan[0]['command'])
- self.assertEqual('component', plan[0]['name'])
- component = plan[0]['component']
- self.assertEqual('component', component.name())
- self.assertIsNone(component._params)
- self.assertIsNone(component._address)
- self.assertEqual('dispensable', component._kind)
-
- def __do_switch(self, option, value):
- """
- Start it with some component and then switch the configuration of the
- component. This will probably raise, as it is not yet supported.
- """
- configurator = Configurator(self, self.__specials)
- compconfig = {
- 'special': 'test',
- 'process': 'process',
- 'priority': 13,
- 'kind': 'core'
- }
- modifiedconfig = copy.copy(compconfig)
- modifiedconfig[option] = value
- return configurator._build_plan({'comp': (compconfig, None)},
- {'comp': modifiedconfig})
-
- def test_change_config_plan(self):
- """
- Test changing a configuration of one component. This is not yet
- implemented and should therefore throw.
- """
- self.assertRaises(NotImplementedError, self.__do_switch, 'kind',
- 'dispensable')
- self.assertRaises(NotImplementedError, self.__do_switch, 'special',
- 'not_a_test')
- self.assertRaises(NotImplementedError, self.__do_switch, 'process',
- 'different')
- self.assertRaises(NotImplementedError, self.__do_switch, 'address',
- 'different')
- self.assertRaises(NotImplementedError, self.__do_switch, 'params',
- ['different'])
- # This does not change anything on running component, so no need to
- # raise
- self.assertEqual([], self.__do_switch('priority', 5))
- # Check against false positive, if the data are the same, but different
- # instance
- self.assertEqual([], self.__do_switch('special', 'test'))
-
- def __check_shutdown_log(self):
- """
- Checks the log for shutting down from the core configuration.
- """
- # We know everything must be stopped, we know what it is.
- # But we don't know the order, so we check everything is exactly
- # once in the log
- components = set(self.__core.keys())
- for (name, command) in self.log:
- self.assertEqual('stop', command)
- self.assertTrue(name in components)
- components.remove(name)
- self.assertEqual(set([]), components, "Some component wasn't stopped")
-
- def test_run(self):
- """
- Passes some configuration to the startup method and sees if
- the components are started up. Then it reconfigures it with
- empty configuration, the original configuration again and shuts
- down.
-
- It also checks the components are kept inside the configurator.
- """
- configurator = Configurator(self, self.__specials)
- # Can't reconfigure nor stop yet
- self.assertRaises(ValueError, configurator.reconfigure, self.__core)
- self.assertRaises(ValueError, configurator.shutdown)
- self.assertFalse(configurator.running())
- # Start it
- configurator.startup(self.__core)
- self.assertEqual(self.__core_log, self.log)
- for core in self.__core.keys():
- self.assertTrue(core in configurator._components)
- self.assertEqual(self.__core[core],
- configurator._components[core][0])
- self.assertEqual(set(self.__core), set(configurator._components))
- self.assertTrue(configurator.running())
- # It can't be started twice
- self.assertRaises(ValueError, configurator.startup, self.__core)
-
- self.log = []
- # Reconfigure - stop everything
- configurator.reconfigure({})
- self.assertEqual({}, configurator._components)
- self.assertTrue(configurator.running())
- self.__check_shutdown_log()
-
- # Start it again
- self.log = []
- configurator.reconfigure(self.__core)
- self.assertEqual(self.__core_log, self.log)
- for core in self.__core.keys():
- self.assertTrue(core in configurator._components)
- self.assertEqual(self.__core[core],
- configurator._components[core][0])
- self.assertEqual(set(self.__core), set(configurator._components))
- self.assertTrue(configurator.running())
-
- # Do a shutdown
- self.log = []
- configurator.shutdown()
- self.assertEqual({}, configurator._components)
- self.assertFalse(configurator.running())
- self.__check_shutdown_log()
-
- # It can't be stopped twice
- self.assertRaises(ValueError, configurator.shutdown)
-
- def test_sort_no_prio(self):
- """
- There was a bug if there were two things with the same priority
- (or without priority), it failed as it couldn't compare the dicts
- there. This tests it doesn't crash.
- """
- configurator = Configurator(self, self.__specials)
- configurator._build_plan({}, {
- "c1": { 'kind': 'dispensable'},
- "c2": { 'kind': 'dispensable'}
- })
-
-if __name__ == '__main__':
- isc.log.init("bind10") # FIXME Should this be needed?
- isc.log.resetUnitTestRootLogger()
- unittest.main()
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-Tests for the bind10.sockcreator module.
-"""
-
-import unittest
-import struct
-import socket
-from isc.net.addr import IPAddr
-import isc.log
-from libutil_io_python import send_fd
-from isc.bind10.sockcreator import Parser, CreatorError, WrappedSocket
-
-class FakeCreator:
- """
- Class emulating the socket to the socket creator. It can be given expected
- data to receive (and check) and responses to give to the Parser class
- during testing.
- """
-
- class InvalidPlan(Exception):
- """
- Raised when someone wants to recv when sending is planned or vice
- versa.
- """
- pass
-
- class InvalidData(Exception):
- """
- Raises when the data passed to sendall are not the same as expected.
- """
- pass
-
- def __init__(self, plan):
- """
- Create the object. The plan variable contains list of expected actions,
- in form:
-
- [('r', 'Data to return from recv'), ('s', 'Data expected on sendall'),
- , ('d', 'File descriptor number to return from read_sock'), ('e',
- None), ...]
-
- It modifies the array as it goes.
- """
- self.__plan = plan
-
- def __get_plan(self, expected):
- if len(self.__plan) == 0:
- raise InvalidPlan('Nothing more planned')
- (kind, data) = self.__plan[0]
- if kind == 'e':
- self.__plan.pop(0)
- raise socket.error('False socket error')
- if kind != expected:
- raise InvalidPlan('Planned ' + kind + ', but ' + expected +
- 'requested')
- return data
-
- def recv(self, maxsize):
- """
- Emulate recv. Returs maxsize bytes from the current recv plan. If
- there are data left from previous recv call, it is used first.
-
- If no recv is planned, raises InvalidPlan.
- """
- data = self.__get_plan('r')
- result, rest = data[:maxsize], data[maxsize:]
- if len(rest) > 0:
- self.__plan[0] = ('r', rest)
- else:
- self.__plan.pop(0)
- return result
-
- def read_fd(self):
- """
- Emulate the reading of file descriptor. Returns one from a plan.
-
- It raises InvalidPlan if no socket is planned now.
- """
- fd = self.__get_plan('f')
- self.__plan.pop(0)
- return fd
-
- def sendall(self, data):
- """
- Checks that the data passed are correct according to plan. It raises
- InvalidData if the data differs or InvalidPlan when sendall is not
- expected.
- """
- planned = self.__get_plan('s')
- dlen = len(data)
- prefix, rest = planned[:dlen], planned[dlen:]
- if prefix != data:
- raise InvalidData('Expected "' + str(prefix)+ '", got "' +
- str(data) + '"')
- if len(rest) > 0:
- self.__plan[0] = ('s', rest)
- else:
- self.__plan.pop(0)
-
- def all_used(self):
- """
- Returns if the whole plan was consumed.
- """
- return len(self.__plan) == 0
-
-class ParserTests(unittest.TestCase):
- """
- Testcases for the Parser class.
-
- A lot of these test could be done by
- `with self.assertRaises(CreatorError) as cm`. But some versions of python
- take the scope wrong and don't work, so we use the primitive way of
- try-except.
- """
- def __terminate(self):
- creator = FakeCreator([('s', b'T'), ('r', b'')])
- parser = Parser(creator)
- self.assertEqual(None, parser.terminate())
- self.assertTrue(creator.all_used())
- return parser
-
- def test_terminate(self):
- """
- Test if the command to terminate is correct and it waits for reading the
- EOF.
- """
- self.__terminate()
-
- def __terminate_raises(self, parser):
- """
- Check that terminate() raises a fatal exception.
- """
- try:
- parser.terminate()
- self.fail("Not raised")
- except CreatorError as ce:
- self.assertTrue(ce.fatal)
- self.assertEqual(None, ce.errno)
-
- def test_terminate_error1(self):
- """
- Test it reports an exception when there's error terminating the creator.
- This one raises an error when receiving the EOF.
- """
- creator = FakeCreator([('s', b'T'), ('e', None)])
- parser = Parser(creator)
- self.__terminate_raises(parser)
-
- def test_terminate_error2(self):
- """
- Test it reports an exception when there's error terminating the creator.
- This one raises an error when sending data.
- """
- creator = FakeCreator([('e', None)])
- parser = Parser(creator)
- self.__terminate_raises(parser)
-
- def test_terminate_error3(self):
- """
- Test it reports an exception when there's error terminating the creator.
- This one sends data when it should have terminated.
- """
- creator = FakeCreator([('s', b'T'), ('r', b'Extra data')])
- parser = Parser(creator)
- self.__terminate_raises(parser)
-
- def test_terminate_twice(self):
- """
- Test we can't terminate twice.
- """
- parser = self.__terminate()
- self.__terminate_raises(parser)
-
- def test_crash(self):
- """
- Tests that the parser correctly raises exception when it crashes
- unexpectedly.
- """
- creator = FakeCreator([('s', b'SU4\0\0\0\0\0\0'), ('r', b'')])
- parser = Parser(creator)
- try:
- parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
- self.fail("Not raised")
- except CreatorError as ce:
- self.assertTrue(creator.all_used())
- # Is the exception correct?
- self.assertTrue(ce.fatal)
- self.assertEqual(None, ce.errno)
-
- def test_error(self):
- """
- Tests that the parser correctly raises non-fatal exception when
- the socket can not be created.
- """
- # We split the int to see if it can cope with data coming in
- # different packets
- intpart = struct.pack('@i', 42)
- creator = FakeCreator([('s', b'SU4\0\0\0\0\0\0'), ('r', b'ES' +
- intpart[:1]), ('r', intpart[1:])])
- parser = Parser(creator)
- try:
- parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
- self.fail("Not raised")
- except CreatorError as ce:
- self.assertTrue(creator.all_used())
- # Is the exception correct?
- self.assertFalse(ce.fatal)
- self.assertEqual(42, ce.errno)
-
- def __error(self, plan):
- creator = FakeCreator(plan)
- parser = Parser(creator)
- try:
- parser.get_socket(IPAddr('0.0.0.0'), 0, socket.SOCK_DGRAM)
- self.fail("Not raised")
- except CreatorError as ce:
- self.assertTrue(creator.all_used())
- self.assertTrue(ce.fatal)
-
- def test_error_send(self):
- self.__error([('e', None)])
-
- def test_error_recv(self):
- self.__error([('s', b'SU4\0\0\0\0\0\0'), ('e', None)])
-
- def test_error_read_fd(self):
- self.__error([('s', b'SU4\0\0\0\0\0\0'), ('r', b'S'), ('e', None)])
-
- def __create(self, addr, socktype, encoded):
- creator = FakeCreator([('s', b'S' + encoded), ('r', b'S'), ('f', 42)])
- parser = Parser(creator)
- self.assertEqual(42, parser.get_socket(IPAddr(addr), 42, socktype))
-
- def test_create1(self):
- self.__create('192.0.2.0', 'UDP', b'U4\0\x2A\xC0\0\x02\0')
-
- def test_create2(self):
- self.__create('2001:db8::', socket.SOCK_STREAM,
- b'T6\0\x2A\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\0\0')
-
- def test_create_terminated(self):
- """
- Test we can't request sockets after it was terminated.
- """
- parser = self.__terminate()
- try:
- parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
- self.fail("Not raised")
- except CreatorError as ce:
- self.assertTrue(ce.fatal)
- self.assertEqual(None, ce.errno)
-
- def test_invalid_socktype(self):
- """
- Test invalid socket type is rejected
- """
- self.assertRaises(ValueError, Parser(FakeCreator([])).get_socket,
- IPAddr('0.0.0.0'), 42, 'RAW')
-
- def test_invalid_family(self):
- """
- Test it rejects invalid address family.
- """
- # Note: this produces a bad logger output, since this address
- # can not be converted to string, so the original message with
- # placeholders is output. This should not happen in practice, so
- # it is harmless.
- addr = IPAddr('0.0.0.0')
- addr.family = 42
- self.assertRaises(ValueError, Parser(FakeCreator([])).get_socket,
- addr, 42, socket.SOCK_DGRAM)
-
-class WrapTests(unittest.TestCase):
- """
- Tests for the wrap_socket function.
- """
- def test_wrap(self):
- # We construct two pairs of socket. The receiving side of one pair will
- # be wrapped. Then we send one of the other pair through this pair and
- # check the received one can be used as a socket
-
- # The transport socket
- (t1, t2) = socket.socketpair()
- # The payload socket
- (p1, p2) = socket.socketpair()
-
- t2 = WrappedSocket(t2)
-
- # Transfer the descriptor
- send_fd(t1.fileno(), p1.fileno())
- p1.close()
- p1 = socket.fromfd(t2.read_fd(), socket.AF_UNIX, socket.SOCK_STREAM)
-
- # Now, pass some data through the socket
- p1.send(b'A')
- data = p2.recv(1)
- self.assertEqual(b'A', data)
-
- # Test the wrapping didn't hurt the socket's usual methods
- t1.send(b'B')
- data = t2.recv(1)
- self.assertEqual(b'B', data)
- t2.send(b'C')
- data = t1.recv(1)
- self.assertEqual(b'C', data)
-
- # Explicitly close temporary socket pair as the Python
- # interpreter expects it. It may not be 100% exception safe,
- # but since this is only for tests we prefer brevity.
- p1.close()
- p2.close()
- t1.close()
- t2.close()
-
-if __name__ == '__main__':
- isc.log.init("bind10") # FIXME Should this be needed?
- isc.log.resetUnitTestRootLogger()
- unittest.main()
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import unittest
-import isc.log
-import isc.bind10.socket_cache
-import isc.bind10.sockcreator
-from isc.net.addr import IPAddr
-import os
-
-class Test(unittest.TestCase):
- """
- Base for the tests here. It replaces the os.close method.
- """
- def setUp(self):
- self._closes = []
- isc.bind10.socket_cache.os.close = self.__close
-
- def tearDown(self):
- # This is not very clean solution. But when the test stops
- # to exist, the method must not be used to destroy the
- # object any more. And we can't restore the os.close here
- # as we never work with real sockets here.
- isc.bind10.socket_cache.os.close = lambda fd: None
-
- def __close(self, fd):
- """
- Just log a close was called.
- """
- self._closes.append(fd)
-
-class SocketTest(Test):
- """
- Test for the Socket class.
- """
- def setUp(self):
- """
- Creates the socket to be tested.
-
- It also creates other useful test variables.
- """
- Test.setUp(self)
- self.__address = IPAddr("192.0.2.1")
- self.__socket = isc.bind10.socket_cache.Socket('UDP', self.__address,
- 1024, 42)
-
- def test_init(self):
- """
- Checks the internals of the cache just after the creation.
- """
- self.assertEqual('UDP', self.__socket.protocol)
- self.assertEqual(self.__address, self.__socket.address)
- self.assertEqual(1024, self.__socket.port)
- self.assertEqual(42, self.__socket.fileno)
- self.assertEqual({}, self.__socket.active_tokens)
- self.assertEqual({}, self.__socket.shares)
- self.assertEqual(set(), self.__socket.waiting_tokens)
-
- def test_del(self):
- """
- Check it closes the socket when removed.
- """
- # This should make the refcount 0 and call the destructor
- # right away
- self.__socket = None
- self.assertEqual([42], self._closes)
-
- def test_share_modes(self):
- """
- Test the share mode compatibility check function.
- """
- modes = ['NO', 'SAMEAPP', 'ANY']
- # If there are no shares, it is compatible with everything.
- for mode in modes:
- self.assertTrue(self.__socket.share_compatible(mode, 'anything'))
-
- # There's an NO already, so it is incompatible with everything.
- self.__socket.shares = {'token': ('NO', 'anything')}
- for mode in modes:
- self.assertFalse(self.__socket.share_compatible(mode, 'anything'))
-
- # If there's SAMEAPP, it is compatible with ANY and SAMEAPP with the
- # same name.
- self.__socket.shares = {'token': ('SAMEAPP', 'app')}
- self.assertFalse(self.__socket.share_compatible('NO', 'app'))
- self.assertFalse(self.__socket.share_compatible('SAMEAPP',
- 'something'))
- self.assertTrue(self.__socket.share_compatible('SAMEAPP', 'app'))
- self.assertTrue(self.__socket.share_compatible('ANY', 'app'))
- self.assertFalse(self.__socket.share_compatible('ANY', 'something'))
-
- # If there's ANY, then ANY and SAMEAPP with the same name is compatible
- self.__socket.shares = {'token': ('ANY', 'app')}
- self.assertFalse(self.__socket.share_compatible('NO', 'app'))
- self.assertFalse(self.__socket.share_compatible('SAMEAPP',
- 'something'))
- self.assertTrue(self.__socket.share_compatible('SAMEAPP', 'app'))
- self.assertTrue(self.__socket.share_compatible('ANY', 'something'))
-
- # In case there are multiple already inside
- self.__socket.shares = {
- 'token': ('ANY', 'app'),
- 'another': ('SAMEAPP', 'app')
- }
- self.assertFalse(self.__socket.share_compatible('NO', 'app'))
- self.assertFalse(self.__socket.share_compatible('SAMEAPP',
- 'something'))
- self.assertTrue(self.__socket.share_compatible('SAMEAPP', 'app'))
- self.assertFalse(self.__socket.share_compatible('ANY', 'something'))
- self.assertTrue(self.__socket.share_compatible('ANY', 'app'))
-
- # Invalid inputs are rejected
- self.assertRaises(ValueError, self.__socket.share_compatible, 'bad',
- 'bad')
-
-class SocketCacheTest(Test):
- """
- Some tests for the isc.bind10.socket_cache.Cache.
-
- This class, as well as being the testcase, pretends to be the
- socket creator so it can hijack all the requests for sockets.
- """
- def setUp(self):
- """
- Creates the cache for tests with us being the socket creator.
-
- Also creates some more variables for testing.
- """
- Test.setUp(self)
- self.__cache = isc.bind10.socket_cache.Cache(self)
- self.__address = IPAddr("192.0.2.1")
- self.__socket = isc.bind10.socket_cache.Socket('UDP', self.__address,
- 1024, 42)
- self.__get_socket_called = False
-
- def test_init(self):
- """
- Checks the internals of the cache just after the creation.
- """
- self.assertEqual(self, self.__cache._creator)
- self.assertEqual({}, self.__cache._waiting_tokens)
- self.assertEqual({}, self.__cache._active_tokens)
- self.assertEqual({}, self.__cache._active_apps)
- self.assertEqual({}, self.__cache._sockets)
- self.assertEqual(set(), self.__cache._live_tokens)
-
- def get_socket(self, address, port, socktype):
- """
- Pretend to be a socket creator.
-
- This expects to be called with the _address, port 1024 and 'UDP'.
-
- Returns 42 and notes down it was called.
- """
- self.assertEqual(self.__address, address)
- self.assertEqual(1024, port)
- self.assertEqual('UDP', socktype)
- self.__get_socket_called = True
- return 42
-
- def test_get_token_cached(self):
- """
- Check the behaviour of get_token when the requested socket is already
- cached inside.
- """
- self.__cache._sockets = {
- 'UDP': {'192.0.2.1': {1024: self.__socket}}
- }
- token = self.__cache.get_token('UDP', self.__address, 1024, 'ANY',
- 'test')
- # It didn't call get_socket
- self.assertFalse(self.__get_socket_called)
- # It returned something
- self.assertIsNotNone(token)
- # The token is both in the waiting sockets and the live tokens
- self.assertEqual({token: self.__socket}, self.__cache._waiting_tokens)
- self.assertEqual(set([token]), self.__cache._live_tokens)
- # The token got the new share to block any relevant queries
- self.assertEqual({token: ('ANY', 'test')}, self.__socket.shares)
- # The socket knows the token is waiting in it
- self.assertEqual(set([token]), self.__socket.waiting_tokens)
-
- # If we request one more, with incompatible share, it is rejected
- self.assertRaises(isc.bind10.socket_cache.ShareError,
- self.__cache.get_token, 'UDP', self.__address, 1024,
- 'NO', 'test')
- # The internals are not changed, so the same checks
- self.assertEqual({token: self.__socket}, self.__cache._waiting_tokens)
- self.assertEqual(set([token]), self.__cache._live_tokens)
- self.assertEqual({token: ('ANY', 'test')}, self.__socket.shares)
- self.assertEqual(set([token]), self.__socket.waiting_tokens)
-
- def test_get_token_uncached(self):
- """
- Check a new socket is created when a corresponding one is missing.
- """
- token = self.__cache.get_token('UDP', self.__address, 1024, 'ANY',
- 'test')
- # The get_socket was called
- self.assertTrue(self.__get_socket_called)
- # It returned something
- self.assertIsNotNone(token)
- # Get the socket and check it looks OK
- socket = self.__cache._waiting_tokens[token]
- self.assertEqual(self.__address, socket.address)
- self.assertEqual(1024, socket.port)
- self.assertEqual(42, socket.fileno)
- self.assertEqual('UDP', socket.protocol)
- # The socket is properly cached
- self.assertEqual({
- 'UDP': {'192.0.2.1': {1024: socket}}
- }, self.__cache._sockets)
- # The token is both in the waiting sockets and the live tokens
- self.assertEqual({token: socket}, self.__cache._waiting_tokens)
- self.assertEqual(set([token]), self.__cache._live_tokens)
- # The token got the new share to block any relevant queries
- self.assertEqual({token: ('ANY', 'test')}, socket.shares)
- # The socket knows the token is waiting in it
- self.assertEqual(set([token]), socket.waiting_tokens)
-
- def test_get_token_excs(self):
- """
- Test that it is handled properly if the socket creator raises
- some exceptions.
- """
- def raiseCreatorError(fatal):
- raise isc.bind10.sockcreator.CreatorError('test error', fatal)
- # First, fatal socket creator errors are passed through
- self.get_socket = lambda addr, port, proto: raiseCreatorError(True)
- self.assertRaises(isc.bind10.sockcreator.CreatorError,
- self.__cache.get_token, 'UDP', self.__address, 1024,
- 'NO', 'test')
- # And nonfatal are converted to SocketError
- self.get_socket = lambda addr, port, proto: raiseCreatorError(False)
- self.assertRaises(isc.bind10.socket_cache.SocketError,
- self.__cache.get_token, 'UDP', self.__address, 1024,
- 'NO', 'test')
-
- def test_get_socket(self):
- """
- Test that we can pickup a socket if we know a token.
- """
- token = "token"
- app = 13
- # No socket prepared there
- self.assertRaises(ValueError, self.__cache.get_socket, token, app)
- # Not changed
- self.assertEqual({}, self.__cache._active_tokens)
- self.assertEqual({}, self.__cache._active_apps)
- self.assertEqual({}, self.__cache._sockets)
- self.assertEqual(set(), self.__cache._live_tokens)
- # Prepare a token there
- self.__socket.waiting_tokens = set([token])
- self.__socket.shares = {token: ('ANY', 'app')}
- self.__cache._waiting_tokens = {token: self.__socket}
- self.__cache._sockets = {'UDP': {'192.0.2.1': {1024: self.__socket}}}
- self.__cache._live_tokens = set([token])
- socket = self.__cache.get_socket(token, app)
- # Received the fileno
- self.assertEqual(42, socket)
- # It moved from waiting to active ones
- self.assertEqual({}, self.__cache._waiting_tokens)
- self.assertEqual({token: self.__socket}, self.__cache._active_tokens)
- self.assertEqual({13: set([token])}, self.__cache._active_apps)
- self.assertEqual(set([token]), self.__cache._live_tokens)
- self.assertEqual(set(), self.__socket.waiting_tokens)
- self.assertEqual({token: 13}, self.__socket.active_tokens)
- # Trying to get it again fails
- self.assertRaises(ValueError, self.__cache.get_socket, token, app)
-
- def test_drop_application(self):
- """
- Test that a drop_application calls drop_socket on all the sockets
- held by the application.
- """
- sockets = set()
- def drop_socket(token):
- sockets.add(token)
- # Mock the drop_socket so we know it is called
- self.__cache.drop_socket = drop_socket
- self.assertRaises(ValueError, self.__cache.drop_application,
- 13)
- self.assertEqual(set(), sockets)
- # Put the tokens into active_apps. Nothing else should be touched
- # by this call, so leave it alone.
- self.__cache._active_apps = {
- 1: set(['t1', 't2']),
- 2: set(['t3'])
- }
- self.__cache.drop_application(1)
- # We don't check the _active_apps, as it would be cleaned by
- # drop_socket and we removed it.
- self.assertEqual(set(['t1', 't2']), sockets)
-
- def test_drop_socket(self):
- """
- Test the drop_socket call. It tests:
- * That a socket that still has something to keep it alive is left alive
- (both waiting and active).
- * If not, it is deleted.
- * All bookkeeping data around are properly removed.
- * Of course the exception.
- """
- self.assertRaises(ValueError, self.__cache.drop_socket, "bad token")
- self.__socket.active_tokens = {'t1': 1}
- self.__socket.waiting_tokens = set(['t2'])
- self.__socket.shares = {'t1': ('ANY', 'app1'), 't2': ('ANY', 'app2')}
- self.__cache._waiting_tokens = {'t2': self.__socket}
- self.__cache._active_tokens = {'t1': self.__socket}
- self.__cache._sockets = {'UDP': {'192.0.2.1': {1024: self.__socket}}}
- self.__cache._live_tokens = set(['t1', 't2'])
- self.__cache._active_apps = {1: set(['t1'])}
- # We can't drop what wasn't picket up yet
- self.assertRaises(ValueError, self.__cache.drop_socket, 't2')
- self.assertEqual({'t1': 1}, self.__socket.active_tokens)
- self.assertEqual(set(['t2']), self.__socket.waiting_tokens)
- self.assertEqual({'t1': ('ANY', 'app1'), 't2': ('ANY', 'app2')},
- self.__socket.shares)
- self.assertEqual({'t2': self.__socket}, self.__cache._waiting_tokens)
- self.assertEqual({'t1': self.__socket}, self.__cache._active_tokens)
- self.assertEqual({'UDP': {'192.0.2.1': {1024: self.__socket}}},
- self.__cache._sockets)
- self.assertEqual(set(['t1', 't2']), self.__cache._live_tokens)
- self.assertEqual({1: set(['t1'])}, self.__cache._active_apps)
- self.assertEqual([], self._closes)
- # If we drop this, it survives because it waits for being picked up
- self.__cache.drop_socket('t1')
- self.assertEqual({}, self.__socket.active_tokens)
- self.assertEqual(set(['t2']), self.__socket.waiting_tokens)
- self.assertEqual({'t2': ('ANY', 'app2')}, self.__socket.shares)
- self.assertEqual({}, self.__cache._active_tokens)
- self.assertEqual({'UDP': {'192.0.2.1': {1024: self.__socket}}},
- self.__cache._sockets)
- self.assertEqual(set(['t2']), self.__cache._live_tokens)
- self.assertEqual({}, self.__cache._active_apps)
- self.assertEqual([], self._closes)
- # Fill it again, now two applications having the same socket
- self.__socket.active_tokens = {'t1': 1, 't2': 2}
- self.__socket.waiting_tokens = set()
- self.__socket.shares = {'t1': ('ANY', 'app1'), 't2': ('ANY', 'app2')}
- self.__cache._waiting_tokens = {}
- self.__cache._active_tokens = {
- 't1': self.__socket,
- 't2': self.__socket
- }
- self.__cache._live_tokens = set(['t1', 't2', 't3'])
- self.assertEqual([], self._closes)
- # We cheat here little bit, the t3 doesn't exist anywhere else, but
- # we need to check the app isn't removed too soon and it shouldn't
- # matter anywhere else, so we just avoid the tiresome filling in
- self.__cache._active_apps = {1: set(['t1', 't3']), 2: set(['t2'])}
- # Drop it as t1. It should still live.
- self.__cache.drop_socket('t1')
- self.assertEqual({'t2': 2}, self.__socket.active_tokens)
- self.assertEqual(set(), self.__socket.waiting_tokens)
- self.assertEqual({'t2': ('ANY', 'app2')}, self.__socket.shares)
- self.assertEqual({}, self.__cache._waiting_tokens)
- self.assertEqual({'t2': self.__socket}, self.__cache._active_tokens)
- self.assertEqual({'UDP': {'192.0.2.1': {1024: self.__socket}}},
- self.__cache._sockets)
- self.assertEqual(set(['t3', 't2']), self.__cache._live_tokens)
- self.assertEqual({1: set(['t3']), 2: set(['t2'])},
- self.__cache._active_apps)
- self.assertEqual([], self._closes)
- # Drop it again, from the other application. It should get removed
- # and closed.
- self.__cache.drop_socket('t2')
- self.assertEqual({}, self.__socket.active_tokens)
- self.assertEqual(set(), self.__socket.waiting_tokens)
- self.assertEqual({}, self.__socket.shares)
- self.assertEqual({}, self.__cache._waiting_tokens)
- self.assertEqual({}, self.__cache._active_tokens)
- self.assertEqual({}, self.__cache._sockets)
- self.assertEqual(set(['t3']), self.__cache._live_tokens)
- self.assertEqual({1: set(['t3'])}, self.__cache._active_apps)
- # The cache doesn't hold the socket. So when we remove it ourself,
- # it should get closed.
- self.__socket = None
- self.assertEqual([42], self._closes)
-
-if __name__ == '__main__':
- isc.log.init("bind10")
- isc.log.resetUnitTestRootLogger()
- unittest.main()
+++ /dev/null
-SUBDIRS = . cc_generated tests
-
-python_PYTHON = __init__.py data.py session.py message.py logger.py
-BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/pycc_messages.py
-nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/pycc_messages.py
-pylogmessagedir = $(pyexecdir)/isc/log_messages/
-
-CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/pycc_messages.py
-CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/pycc_messages.pyc
-CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/pycc_messages.pyo
-
-EXTRA_DIST = pycc_messages.mes proto_defs.py
-
-# Define rule to build logging source files from message file
-$(PYTHON_LOGMSGPKG_DIR)/work/pycc_messages.py: pycc_messages.mes
- $(top_builddir)/src/lib/log/compiler/message \
- -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/pycc_messages.mes
-
-pythondir = $(pyexecdir)/isc/cc
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-import isc.cc.message
-from isc.cc.session import *
+++ /dev/null
-/__init__.py
-/proto_defs.py
+++ /dev/null
-# This makefile is a hack to enable tests to run with one module generated
-# while the rest is just used. The generated file is created under build dir,
-# not the src dir, which means it is not found when these are different.
-#
-# We have a forwarder module in the src dir and build the real one in different
-# location. This is similar to what happens in log_messages/work. We can't
-# reuse the name `work`, since it would collide, so we use less generic name.
-
-nodist_python_PYTHON = proto_defs.py
-BUILT_SOURCES = proto_defs.py __init__.py
-noinst_SCRIPTS = __init__.py
-
-proto_defs.py: $(top_srcdir)/src/lib/cc/proto_defs.cc \
- $(top_srcdir)/src/lib/util/python/pythonize_constants.py
- $(PYTHON) $(top_srcdir)/src/lib/util/python/pythonize_constants.py \
- $(top_srcdir)/src/lib/cc/proto_defs.cc $@
-
-# We need to create an __init__.py, so it is recognized as module.
-# But it may be empty.
-__init__.py:
- touch $@
-
-pythondir = $(pyexecdir)/isc/cc
-
-CLEANDIRS = __pycache__
-
-CLEANFILES = proto_defs.py __init__.py
-CLEANFILES += proto_defs.pyc __init__.pyc
-CLEANFILES += proto_defs.pyo __init__.pyo
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Helper functions for data elements as used in cc-channel and
-# configuration. There is no python equivalent for the cpp Element
-# class, since data elements are represented by native python types
-# (int, real, bool, string, list and dict respectively)
-#
-
-import json
-import re
-
-class DataNotFoundError(Exception):
- """Raised if an identifier does not exist according to a spec file,
- or if an item is addressed that is not in the current (or default)
- config (such as a nonexistent list or map element)"""
- pass
-
-class DataAlreadyPresentError(Exception):
- """Raised if there is an attemt to add an element to a list or a
- map that is already present in that list or map (i.e. if 'add'
- is used when it should be 'set')"""
- pass
-
-class DataTypeError(Exception):
- """Raised if there is an attempt to set an element that is of a
- different type than the type specified in the specification."""
- pass
-
-def remove_identical(a, b):
- """Removes the values from dict a that are the same as in dict b.
- Raises a DataTypeError is a or b is not a dict"""
- to_remove = []
- if type(a) != dict or type(b) != dict:
- raise DataTypeError("Not a dict in remove_identical()")
- duplicate_keys = [key for key in a.keys() if key in b and a[key] == b[key]]
- for id in duplicate_keys:
- del(a[id])
-
-def merge(orig, new):
- """Merges the contents of new into orig, think recursive update()
- orig and new must both be dicts. If an element value is None in
- new it will be removed in orig."""
- if type(orig) != dict or type(new) != dict:
- raise DataTypeError("Not a dict in merge()")
- orig.update(new)
- remove_null_items(orig)
-
-def remove_null_items(d):
- """Recursively removes all (key,value) pairs from d where the
- value is None"""
- null_keys = []
- for key in d.keys():
- if type(d[key]) == dict:
- remove_null_items(d[key])
- elif d[key] is None:
- null_keys.append(key)
- for k in null_keys:
- del d[k]
-
-def _concat_identifier(id_parts):
- """Concatenates the given identifier parts into a string,
- delimited with the '/' character.
- """
- return '/'.join(id_parts)
-
-def split_identifier(identifier):
- """Splits the given identifier into a list of identifier parts,
- as delimited by the '/' character.
- Raises a DataTypeError if identifier is not a string."""
- if type(identifier) != str:
- raise DataTypeError("identifier is not a string")
- id_parts = identifier.split('/')
- id_parts[:] = (value for value in id_parts if value != "")
- return id_parts
-
-def identifier_has_list_index(identifier):
- """Returns True if the given identifier string has at least one
- list index (with [I], where I is a number"""
- return (type(identifier) == str and
- re.search("\[\d+\]", identifier) is not None)
-
-
-def split_identifier_list_indices(identifier):
- """Finds list indexes in the given identifier, which are of the
- format [integer].
- Identifier must be a string.
- This will only give the list index for the last 'part' of the
- given identifier (as delimited by the '/' sign).
- Raises a DataTypeError if the identifier is not a string,
- or if the format is bad.
- Returns a tuple, where the first element is the string part of
- the identifier, and the second element is a list of (nested) list
- indices.
- Examples:
- 'a/b/c' will return ('a/b/c', None)
- 'a/b/c[1]' will return ('a/b/c', [1])
- 'a/b/c[1][2][3]' will return ('a/b/c', [1, 2, 3])
- 'a[0]/b[1]/c[2]' will return ('a[0]/b[1]/c', [2])
- """
- if type(identifier) != str:
- raise DataTypeError("identifier in "
- "split_identifier_list_indices() "
- "not a string: " + str(identifier))
-
- # We only work on the final 'part' of the identifier
- id_parts = split_identifier(identifier)
- id_str = id_parts[-1]
-
- i = id_str.find('[')
- if i < 0:
- if id_str.find(']') >= 0:
- raise DataTypeError("Bad format in identifier (] but no [): " + str(identifier))
- return identifier, None
-
- # keep the non-index part of that to replace later
- id = id_str[:i]
- indices = []
- while i >= 0:
- e = id_str.find(']')
- if e < i + 1:
- raise DataTypeError("Bad format in identifier (] before [): " + str(identifier))
- try:
- indices.append(int(id_str[i+1:e]))
- except ValueError:
- raise DataTypeError("List index in " + identifier + " not an integer")
- id_str = id_str[e + 1:]
- i = id_str.find('[')
- if i > 0:
- raise DataTypeError("Bad format in identifier ([ within []): " + str(identifier))
- if id.find(']') >= 0 or len(id_str) > 0:
- raise DataTypeError("Bad format in identifier (extra ]): " + str(identifier))
-
- # we replace the final part of the original identifier with
- # the stripped string
- id_parts[-1] = id
- id = _concat_identifier(id_parts)
- return id, indices
-
-def _find_child_el(element, id):
- """Finds the child of element with the given id. If the id contains
- [i], where i is a number, and the child element is a list, the
- i-th element of that list is returned instead of the list itself.
- Raises a DataTypeError if the element is of wrong type, if id
- is not a string, or if the id string contains a bad value.
- Raises a DataNotFoundError if the element at id could not be
- found.
- """
- id, list_indices = split_identifier_list_indices(id)
- if type(element) == dict and id in element.keys():
- result = element[id]
- else:
- raise DataNotFoundError(id + " in " + str(element))
- if type(result) == list and list_indices is not None:
- for list_index in list_indices:
- if list_index >= len(result):
- raise DataNotFoundError("Element " + str(list_index) + " in " + str(result))
- result = result[list_index]
- return result
-
-def find(element, identifier):
- """Returns the subelement in the given data element, raises
- DataNotFoundError if not found.
- Returns the given element if the identifier is an empty string.
- Raises a DataTypeError if identifier is not a string, or if
- identifier is not empty, and element is not a dict.
- """
- if type(identifier) != str:
- raise DataTypeError("identifier in find() is not a str")
- if identifier == "":
- return element
- if type(element) != dict:
- raise DataTypeError("element in find() is not a dict")
- id_parts = split_identifier(identifier)
- cur_el = element
- for id in id_parts:
- cur_el = _find_child_el(cur_el, id)
- return cur_el
-
-def set(element, identifier, value):
- """Sets the value at the element specified by identifier to value.
- If the value is None, it is removed from the dict. If element
- is not a dict, or if the identifier points to something that is
- not, a DataTypeError is raised. The element is updated inline,
- so if the original needs to be kept, you must make a copy before
- calling set(). The updated base element is returned (so that
- el.set().set().set() is possible)"""
- if type(element) != dict:
- raise DataTypeError("element in set() is not a dict")
- if type(identifier) != str:
- raise DataTypeError("identifier in set() is not a str")
- id_parts = split_identifier(identifier)
- cur_el = element
- for id in id_parts[:-1]:
- try:
- cur_el = _find_child_el(cur_el, id)
- except DataNotFoundError:
- if value is None:
- # ok we are unsetting a value that wasn't set in
- # the first place. Simply stop.
- return
- cur_el[id] = {}
- cur_el = cur_el[id]
-
- id, list_indices = split_identifier_list_indices(id_parts[-1])
- if list_indices is None:
- # value can be an empty list or dict, so check for None explicitly
- if value is not None:
- cur_el[id] = value
- else:
- del cur_el[id]
- else:
- cur_el = cur_el[id]
- # in case of nested lists, we need to get to the next to last
- for list_index in list_indices[:-1]:
- if type(cur_el) != list:
- raise DataTypeError("Element at " + identifier + " is not a list")
- if len(cur_el) <= list_index:
- raise DataNotFoundError("List index at " + identifier + " out of range")
- cur_el = cur_el[list_index]
- # value can be an empty list or dict, so check for None explicitly
- list_index = list_indices[-1]
- if type(cur_el) != list:
- raise DataTypeError("Element at " + identifier + " is not a list")
- if len(cur_el) <= list_index:
- raise DataNotFoundError("List index at " + identifier + " out of range")
- if value is not None:
- cur_el[list_index] = value
- else:
- del cur_el[list_index]
- return element
-
-def unset(element, identifier):
- """Removes the element at the given identifier if it exists. Raises
- a DataTypeError if element is not a dict or if identifier is not
- a string. Returns the base element."""
- # perhaps we can simply do with set none, and remove this whole
- # function
- return set(element, identifier, None)
-
-def find_no_exc(element, identifier):
- """Returns the subelement in the given data element, returns None
- if not found, or if an error occurred (i.e. this function should
- never raise an exception)"""
- try:
- return find(element, identifier)
- except DataNotFoundError:
- return None
- except DataTypeError:
- return None
-
-def parse_value_str(value_str):
- """Parses the given string to a native python object. If the
- string cannot be parsed, it is returned. If it is not a string,
- None is returned"""
- if type(value_str) != str:
- return None
- try:
- return json.loads(value_str)
- except ValueError as ve:
- # simply return the string itself
- return value_str
-
+++ /dev/null
-# Copyright (C) 2013 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-""" This is a logging utility module for other modules of the cc library
-package.
-
-"""
-
-import isc.log
-
-# C++ version of the CC module uses 'cc'; using the same name does not
-# necessarily cause disruption, but we use a different name to minimize
-# possible confusion.
-logger = isc.log.Logger('pycc')
+++ /dev/null
-# Copyright (C) 2009 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Functions for reading and parsing cc messages
-# Currently these are only abstraction functions for JSON conversion.
-#
-
-import sys
-import struct
-
-import json
-
-def to_wire(items):
- '''Encodes the given python structure in JSON, and converts the
- result to bytes. Raises a TypeError if the given structure is
- not serializable with JSON.'''
- return json.dumps(items).encode('utf8')
-
-def from_wire(data):
- '''Decodes the given bytes and parses it with the builtin JSON
- parser. Raises a ValueError if the data is not valid JSON.
- Raises an AttributeError if the given object has no decode()
- method (which should return a string).
- '''
- return json.loads(data.decode('utf8'), strict=False)
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
+++ /dev/null
-# Forwarder module. Look into cc_generated/Makefile.am for details.
-from cc_generated.proto_defs import *
+++ /dev/null
-# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# No namespace declaration - these constants go in the global namespace
-# of the libddns_messages python module.
-
-% PYCC_LNAME_RECEIVED received local name: %1
-Debug message: the local module received its unique identifier (name)
-from msgq on completion of establishing the session with msgq.
+++ /dev/null
-# Copyright (C) 2009 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import sys
-import socket
-import struct
-import errno
-import os
-import threading
-import bind10_config
-
-import isc.cc.message
-import isc.log
-from isc.cc.logger import logger
-from isc.log_messages.pycc_messages import *
-from isc.cc.proto_defs import *
-
-class ProtocolError(Exception): pass
-class NetworkError(Exception): pass
-class SessionError(Exception): pass
-class SessionTimeout(Exception): pass
-
-class Session:
- MSGQ_DEFAULT_TIMEOUT = 4000
-
- def __init__(self, socket_file=None):
- self._socket = None
- self._lname = None
- self._sequence = 1
- self._closed = False
- self._queue = []
- self._lock = threading.RLock()
- self.set_timeout(self.MSGQ_DEFAULT_TIMEOUT);
- self._recv_len_size = 0
- self._recv_size = 0
-
- if socket_file is None:
- if "BIND10_MSGQ_SOCKET_FILE" in os.environ:
- self.socket_file = os.environ["BIND10_MSGQ_SOCKET_FILE"]
- else:
- self.socket_file = bind10_config.BIND10_MSGQ_SOCKET_FILE
- else:
- self.socket_file = socket_file
-
- try:
- self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- self._socket.connect(self.socket_file)
- self.sendmsg({ CC_HEADER_TYPE: CC_COMMAND_GET_LNAME })
- env, msg = self.recvmsg(False)
- if not env:
- raise ProtocolError("Could not get local name")
- self._lname = msg[CC_PAYLOAD_LNAME]
- if not self._lname:
- raise ProtocolError("Could not get local name")
- logger.debug(logger.DBGLVL_TRACE_BASIC, PYCC_LNAME_RECEIVED,
- self._lname)
- except socket.error as se:
- if self._socket:
- self._socket.close()
- raise SessionError(se)
-
- @property
- def lname(self):
- return self._lname
-
- def close(self):
- self._socket.close()
- self._lname = None
- self._closed = True
-
- def sendmsg(self, env, msg=None):
- with self._lock:
- if self._closed:
- raise SessionError("Session has been closed.")
- if type(env) == dict:
- env = isc.cc.message.to_wire(env)
- if len(env) > 65535:
- raise ProtocolError("Envelope too large")
- if type(msg) == dict:
- msg = isc.cc.message.to_wire(msg)
- length = 2 + len(env);
- if msg is not None:
- length += len(msg)
-
- # Build entire message.
- data = struct.pack("!I", length)
- data += struct.pack("!H", len(env))
- data += env
- if msg is not None:
- data += msg
-
- # Send it in the blocking mode. On some systems send() may
- # actually send only part of the data, so we need to repeat it
- # until all data have been sent out.
- self._socket.setblocking(1)
- while len(data) > 0:
- cc = self._socket.send(data)
- data = data[cc:]
-
- def recvmsg(self, nonblock = True, seq = None):
- """Reads a message. If nonblock is true, and there is no
- message to read, it returns (None, None).
- If seq is not None, it should be a value as returned by
- group_sendmsg(), in which case only the response to
- that message is returned, and others will be queued until
- the next call to this method.
- If seq is None, only messages that are *not* responses
- will be returned, and responses will be queued.
- The queue is checked for relevant messages before data
- is read from the socket.
- Raises a SessionError if there is a JSON decode problem in
- the message that is read, or if the session has been closed
- prior to the call of recvmsg()"""
- with self._lock:
- if len(self._queue) > 0:
- i = 0;
- for env, msg in self._queue:
- if seq != None and CC_HEADER_REPLY in env and \
- seq == env[CC_HEADER_REPLY]:
- return self._queue.pop(i)
- elif seq == None and CC_HEADER_REPLY not in env:
- return self._queue.pop(i)
- else:
- i = i + 1
- if self._closed:
- raise SessionError("Session has been closed.")
- data = self._receive_full_buffer(nonblock)
- if data and len(data) > 2:
- header_length = struct.unpack('>H', data[0:2])[0]
- data_length = len(data) - 2 - header_length
- try:
- if data_length > 0:
- env = isc.cc.message.from_wire(data[2:header_length+2])
- msg = isc.cc.message.from_wire(data[header_length + 2:])
- if (seq == None and CC_HEADER_REPLY not in env) or \
- (seq != None and CC_HEADER_REPLY in env and
- seq == env[CC_HEADER_REPLY]):
- return env, msg
- else:
- self._queue.append((env,msg))
- return self.recvmsg(nonblock, seq)
- else:
- return isc.cc.message.from_wire(data[2:header_length+2]), None
- except ValueError as ve:
- # TODO: when we have logging here, add a debug
- # message printing the data that we were unable
- # to parse as JSON
- raise SessionError(ve)
- return None, None
-
- def _receive_bytes(self, size):
- """Try to get size bytes of data from the socket.
- Raises a ProtocolError if the size is 0.
- Raises any error from recv().
- Returns whatever data was available (if >0 bytes).
- """
- data = self._socket.recv(size)
- if len(data) == 0: # server closed connection
- raise ProtocolError("Read of 0 bytes: connection closed")
- return data
-
- def _receive_len_data(self):
- """Reads self._recv_len_size bytes of data from the socket into
- self._recv_len_data
- This is done through class variables so in the case of
- an EAGAIN we can continue on a subsequent call.
- Raises a ProtocolError, a socket.error (which may be
- timeout or eagain), or reads until we have all data we need.
- """
- while self._recv_len_size > 0:
- new_data = self._receive_bytes(self._recv_len_size)
- self._recv_len_data += new_data
- self._recv_len_size -= len(new_data)
-
- def _receive_data(self):
- """Reads self._recv_size bytes of data from the socket into
- self._recv_data.
- This is done through class variables so in the case of
- an EAGAIN we can continue on a subsequent call.
- Raises a ProtocolError, a socket.error (which may be
- timeout or eagain), or reads until we have all data we need.
- """
- while self._recv_size > 0:
- new_data = self._receive_bytes(self._recv_size)
- self._recv_data += new_data
- self._recv_size -= len(new_data)
-
- def _receive_full_buffer(self, nonblock):
- if nonblock:
- self._socket.setblocking(0)
- else:
- self._socket.setblocking(1)
- if self._socket_timeout == 0.0:
- self._socket.settimeout(None)
- else:
- self._socket.settimeout(self._socket_timeout)
-
- try:
- # we might be in a call following an EAGAIN, in which case
- # we simply continue. In the first case, either
- # recv_size or recv_len size are not zero
- # they may never both be non-zero (we are either starting
- # a full read, or continuing one of the reads
- assert self._recv_size == 0 or self._recv_len_size == 0
-
- if self._recv_size == 0:
- if self._recv_len_size == 0:
- # both zero, start a new full read
- self._recv_len_size = 4
- self._recv_len_data = bytearray()
- self._receive_len_data()
-
- self._recv_size = struct.unpack('>I', self._recv_len_data)[0]
- self._recv_data = bytearray()
- self._receive_data()
-
- # no EAGAIN, so copy data and reset internal counters
- data = self._recv_data
-
- self._recv_len_size = 0
- self._recv_size = 0
-
- return (data)
-
- except socket.timeout:
- raise SessionTimeout("recv() on cc session timed out")
- except socket.error as se:
- # Only keep data in case of EAGAIN
- if se.errno == errno.EAGAIN:
- return None
- # unknown state otherwise, best to drop data
- self._recv_len_size = 0
- self._recv_size = 0
- # ctrl-c can result in EINTR, return None to prevent
- # stacktrace output
- if se.errno == errno.EINTR:
- return None
- raise se
-
- def _next_sequence(self):
- self._sequence += 1
- return self._sequence
-
- def group_subscribe(self, group, instance=CC_INSTANCE_WILDCARD):
- self.sendmsg({
- CC_HEADER_TYPE: CC_COMMAND_SUBSCRIBE,
- CC_HEADER_GROUP: group,
- CC_HEADER_INSTANCE: instance,
- })
-
- def group_unsubscribe(self, group, instance=CC_INSTANCE_WILDCARD):
- self.sendmsg({
- CC_HEADER_TYPE: CC_COMMAND_UNSUBSCRIBE,
- CC_HEADER_GROUP: group,
- CC_HEADER_INSTANCE: instance,
- })
-
- def group_sendmsg(self, msg, group, instance=CC_INSTANCE_WILDCARD,
- to=CC_TO_WILDCARD, want_answer=False):
- '''
- Send a message over the CC session.
-
- Parameters:
- - msg The message to send, encoded as python structures (dicts,
- lists, etc).
- - group The recipient group to send to.
- - instance Instance in the group.
- - to Direct recipient (overrides the above, should contain the
- lname of the recipient).
- - want_answer If an answer is requested. If there's no recipient
- and this is true, the message queue would send an error message
- instead of the answer.
-
- Return:
- A sequence number that can be used to wait for an answer
- (see group_recvmsg).
- '''
- seq = self._next_sequence()
- self.sendmsg({
- CC_HEADER_TYPE: CC_COMMAND_SEND,
- CC_HEADER_FROM: self._lname,
- CC_HEADER_TO: to,
- CC_HEADER_GROUP: group,
- CC_HEADER_INSTANCE: instance,
- CC_HEADER_SEQ: seq,
- CC_HEADER_WANT_ANSWER: want_answer
- }, isc.cc.message.to_wire(msg))
- return seq
-
- def has_queued_msgs(self):
- return len(self._queue) > 0
-
- def group_recvmsg(self, nonblock = True, seq = None):
- env, msg = self.recvmsg(nonblock, seq)
- if env == None:
- # return none twice to match normal return value
- # (so caller won't get a type error on no data)
- return (None, None)
- return (msg, env)
-
- def group_reply(self, routing, msg):
- seq = self._next_sequence()
- self.sendmsg({
- CC_HEADER_TYPE: CC_COMMAND_SEND,
- CC_HEADER_FROM: self._lname,
- CC_HEADER_TO: routing[CC_HEADER_FROM],
- CC_HEADER_GROUP: routing[CC_HEADER_GROUP],
- CC_HEADER_INSTANCE: routing[CC_HEADER_INSTANCE],
- CC_HEADER_SEQ: seq,
- CC_HEADER_REPLY: routing[CC_HEADER_SEQ],
- }, isc.cc.message.to_wire(msg))
- return seq
-
- def set_timeout(self, milliseconds):
- """Sets the socket timeout for blocking reads to the given
- number of milliseconds"""
- self._socket_timeout = milliseconds / 1000.0
-
- def get_timeout(self):
- """Returns the current timeout for blocking reads (in milliseconds)"""
- return self._socket_timeout * 1000.0
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
+++ /dev/null
-PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-
-PYTESTS = message_test.py data_test.py session_test.py
-# NOTE: test_session.py is to be run manually, so not automated.
-EXTRA_DIST = $(PYTESTS)
-EXTRA_DIST += sendcmd.py
-EXTRA_DIST += test_session.py
-
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
-LIBRARY_PATH_PLACEHOLDER =
-if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
-endif
-
-# test using command-line arguments, so use check-local target instead of TESTS
-check-local:
-if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
- rm -f .coverage
- ${LN_S} $(abs_top_srcdir)/.coverage .coverage
-endif
- for pytest in $(PYTESTS) ; do \
- echo Running test: $$pytest ; \
- $(LIBRARY_PATH_PLACEHOLDER) \
- PYTHONPATH=$(COMMON_PYTHON_PATH) \
- BIND10_TEST_SOCKET_FILE=$(builddir)/test_socket.sock \
- $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
- done
+++ /dev/null
-#! /bin/sh
-
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
-export PYTHON_EXEC
-
-CONFIG_PATH=@abs_top_srcdir@/src/lib/python/isc/cc/tests
-
-PYTHONPATH=@abs_top_srcdir@/src/lib/python
-export PYTHONPATH
-
-cd ${BIND10_PATH}
-${PYTHON_EXEC} -O ${CONFIG_PATH}/session_test.py $*
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Tests for the functions in data.py
-#
-
-import unittest
-import os
-from isc.cc import data
-
-class TestData(unittest.TestCase):
- def test_remove_identical(self):
- a = {}
- b = {}
- c = {}
- data.remove_identical(a, b)
- self.assertEqual(a, c)
-
- a = { "a": 1 }
- b = { "a": 1 }
- c = {}
- data.remove_identical(a, b)
- self.assertEqual(a, c)
-
- a = { "a": 1, "b": [ 1, 2 ] }
- b = {}
- c = { "a": 1, "b": [ 1, 2 ] }
- data.remove_identical(a, b)
- self.assertEqual(a, c)
-
- a = { "a": 1, "b": [ 1, 2 ] }
- b = { "a": 1, "b": [ 1, 2 ] }
- c = {}
- data.remove_identical(a, b)
- self.assertEqual(a, c)
-
- a = { "a": 1, "b": [ 1, 2 ] }
- b = { "a": 1, "b": [ 1, 3 ] }
- c = { "b": [ 1, 2 ] }
- data.remove_identical(a, b)
- self.assertEqual(a, c)
-
- a = { "a": { "b": "c" } }
- b = {}
- c = { "a": { "b": "c" } }
- data.remove_identical(a, b)
- self.assertEqual(a, c)
-
- a = { "a": { "b": "c" } }
- b = { "a": { "b": "c" } }
- c = {}
- data.remove_identical(a, b)
- self.assertEqual(a, c)
-
- a = { "a": { "b": "c" } }
- b = { "a": { "b": "d" } }
- c = { "a": { "b": "c" } }
- data.remove_identical(a, b)
- self.assertEqual(a, c)
-
- self.assertRaises(data.DataTypeError, data.remove_identical,
- a, 1)
- self.assertRaises(data.DataTypeError, data.remove_identical,
- 1, b)
-
- def test_merge(self):
- d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
- d2 = { 'a': None, 'c': { 'd': None, 'e': 3, 'f': [ 1 ] } }
- d12 = { 'b': 1, 'c': { 'e': 3, 'f': [ 1 ] } }
- m12 = d1
- data.merge(m12, d2)
- self.assertEqual(d12, m12)
- self.assertRaises(data.DataTypeError, data.merge, d1, "a")
- self.assertRaises(data.DataTypeError, data.merge, 1, d2)
- self.assertRaises(data.DataTypeError, data.merge, None, None)
-
-
- def test_split_identifier_list_indices(self):
- id, indices = data.split_identifier_list_indices('a')
- self.assertEqual(id, 'a')
- self.assertEqual(indices, None)
- id, indices = data.split_identifier_list_indices('a[0]')
- self.assertEqual(id, 'a')
- self.assertEqual(indices, [0])
- id, indices = data.split_identifier_list_indices('a[0][1]')
- self.assertEqual(id, 'a')
- self.assertEqual(indices, [0, 1])
-
- # examples from the docstring
- id, indices = data.split_identifier_list_indices('a/b/c')
- self.assertEqual(id, 'a/b/c')
- self.assertEqual(indices, None)
-
- id, indices = data.split_identifier_list_indices('a/b/c[1]')
- self.assertEqual(id, 'a/b/c')
- self.assertEqual(indices, [1])
-
- id, indices = data.split_identifier_list_indices('a/b/c[1][2][3]')
- self.assertEqual(id, 'a/b/c')
- self.assertEqual(indices, [1, 2, 3])
-
- id, indices = data.split_identifier_list_indices('a[0]/b[1]/c[2]')
- self.assertEqual(id, 'a[0]/b[1]/c')
- self.assertEqual(indices, [2])
-
- # bad formats
- self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a[')
- self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a]')
- self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a[[0]]')
- self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a[0]a')
- self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a[0]a[1]')
-
- self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 1)
-
-
- def test_find(self):
- d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } }
- self.assertEqual(data.find(d1, ''), d1)
- self.assertEqual(data.find(d1, 'a'), 'a')
- self.assertEqual(data.find(d1, 'c/e'), 2)
- self.assertEqual(data.find(d1, 'c/more/'), { 'data': 'here' })
- self.assertEqual(data.find(d1, 'c/more/data'), 'here')
- self.assertRaises(data.DataNotFoundError, data.find, d1, 'c/f')
- self.assertRaises(data.DataNotFoundError, data.find, d1, 'f')
- self.assertRaises(data.DataTypeError, data.find, d1, 1)
- self.assertRaises(data.DataTypeError, data.find, None, 1)
- self.assertRaises(data.DataTypeError, data.find, None, "foo")
- self.assertRaises(data.DataTypeError, data.find, "123", "123")
- self.assertEqual(data.find("123", ""), "123")
-
- d2 = { 'a': [ 1, 2, 3 ] }
- self.assertEqual(data.find(d2, 'a[0]'), 1)
- self.assertEqual(data.find(d2, 'a[1]'), 2)
- self.assertEqual(data.find(d2, 'a[2]'), 3)
- self.assertRaises(data.DataNotFoundError, data.find, d2, 'a[3]')
- self.assertRaises(data.DataTypeError, data.find, d2, 'a[a]')
-
- d3 = { 'a': [ { 'b': [ {}, { 'c': 'd' } ] } ] }
- self.assertEqual(data.find(d3, 'a[0]/b[1]/c'), 'd')
- self.assertRaises(data.DataNotFoundError, data.find, d3, 'a[1]/b[1]/c')
-
- def test_set(self):
- d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
- d12 = { 'b': 1, 'c': { 'e': 3, 'f': [ 1 ] } }
- d13 = { 'b': 1, 'c': { 'e': 3, 'f': [ 2 ] } }
- d14 = { 'b': 1, 'c': { 'e': 3, 'f': [ { 'g': [ 1, 2 ] } ] } }
- d15 = { 'b': 1, 'c': { 'e': 3, 'f': [ { 'g': [ 1, 3 ] } ] } }
- data.set(d1, 'a', None)
- data.set(d1, 'c/d', None)
- data.set(d1, 'c/e/', 3)
- data.set(d1, 'c/f', [ 1 ] )
- self.assertEqual(d1, d12)
- data.set(d1, 'c/f[0]', 2 )
- self.assertEqual(d1, d13)
-
- data.set(d1, 'c/f[0]', { 'g': [ 1, 2] } )
- self.assertEqual(d1, d14)
- data.set(d1, 'c/f[0]/g[1]', 3)
- self.assertEqual(d1, d15)
-
- self.assertRaises(data.DataTypeError, data.set, d1, 1, 2)
- self.assertRaises(data.DataTypeError, data.set, 1, "", 2)
- self.assertRaises(data.DataTypeError, data.set, d1, 'c[1]', 2)
- self.assertRaises(data.DataTypeError, data.set, d1, 'c[1][2]', 2)
- self.assertRaises(data.DataNotFoundError, data.set, d1, 'c/f[5]', 2)
- self.assertRaises(data.DataNotFoundError, data.set, d1, 'c/f[5][2]', 2)
-
- d3 = {}
- e3 = data.set(d3, "does/not/exist", 123)
- self.assertEqual(d3,
- { 'does': { 'not': { 'exist': 123 } } })
- self.assertEqual(e3,
- { 'does': { 'not': { 'exist': 123 } } })
-
- def test_unset(self):
- d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': [ 1, 2, 3 ] } }
- data.unset(d1, 'a')
- data.unset(d1, 'c/d')
- data.unset(d1, 'does/not/exist')
- self.assertEqual(d1, { 'b': 1, 'c': { 'e': [ 1, 2, 3 ] } })
- data.unset(d1, 'c/e[0]')
- self.assertEqual(d1, { 'b': 1, 'c': { 'e': [ 2, 3 ] } })
- data.unset(d1, 'c/e[1]')
- self.assertEqual(d1, { 'b': 1, 'c': { 'e': [ 2 ] } })
- # index 1 should now be out of range
- self.assertRaises(data.DataNotFoundError, data.unset, d1, 'c/e[1]')
- d2 = { 'a': [ { 'b': [ 1, 2 ] } ] }
- data.unset(d2, 'a[0]/b[1]')
- self.assertEqual(d2, { 'a': [ { 'b': [ 1 ] } ] })
- d3 = { 'a': [ [ 1, 2 ] ] }
- data.set(d3, "a[0][1]", 3)
- self.assertEqual(d3, { 'a': [ [ 1, 3 ] ] })
- data.unset(d3, 'a[0][1]')
- self.assertEqual(d3, { 'a': [ [ 1 ] ] })
-
- def test_find_no_exc(self):
- d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } }
- self.assertEqual(data.find_no_exc(d1, ''), d1)
- self.assertEqual(data.find_no_exc(d1, 'a'), 'a')
- self.assertEqual(data.find_no_exc(d1, 'c/e'), 2)
- self.assertEqual(data.find_no_exc(d1, 'c/more/'), { 'data': 'here' })
- self.assertEqual(data.find_no_exc(d1, 'c/more/data'), 'here')
- self.assertEqual(data.find_no_exc(d1, 'c/f'), None)
- self.assertEqual(data.find_no_exc(d1, 'f'), None)
- self.assertEqual(data.find_no_exc(d1, 1), None)
- self.assertEqual(data.find_no_exc(d1, 'more/data/here'), None)
- self.assertEqual(data.find_no_exc(None, 1), None)
- self.assertEqual(data.find_no_exc("123", ""), "123")
- self.assertEqual(data.find_no_exc("123", ""), "123")
-
- def test_parse_value_str(self):
- self.assertEqual(data.parse_value_str("1"), 1)
- self.assertEqual(data.parse_value_str("true"), True)
- self.assertEqual(data.parse_value_str("null"), None)
- self.assertEqual(data.parse_value_str("1.1"), 1.1)
- self.assertEqual(data.parse_value_str("[]"), [])
- self.assertEqual(data.parse_value_str("[ 1, null, \"asdf\" ]"), [ 1, None, "asdf" ])
- self.assertEqual(data.parse_value_str("{}"), {})
- self.assertEqual(data.parse_value_str("{ \"a\": \"b\", \"c\": 1 }"), { 'a': 'b', 'c': 1 })
- self.assertEqual(data.parse_value_str("[ a c"), "[ a c")
-
- self.assertEqual(data.parse_value_str(1), None)
-
-
-if __name__ == '__main__':
- #if not 'CONFIG_TESTDATA_PATH' in os.environ:
- # print("You need to set the environment variable CONFIG_TESTDATA_PATH to point to the directory containing the test data files")
- # exit(1)
- unittest.main()
-
-
+++ /dev/null
-
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Tests for the functions in data.py
-#
-
-import unittest
-import isc.cc
-
-class MessageTest(unittest.TestCase):
- def setUp(self):
- self.msg1 = { "just": [ "an", "arbitrary", "structure" ] }
- self.msg1_str = "{\"just\": [\"an\", \"arbitrary\", \"structure\"]}";
- self.msg1_wire = self.msg1_str.encode()
-
- self.msg2 = { "aaa": [ 1, True, False, None ] }
- self.msg2_str = "{\"aaa\": [1, true, false, null]}";
- self.msg2_wire = self.msg2_str.encode()
-
- self.msg3 = { "aaa": [ 1, 1.1, True, False, "string\n" ] }
- self.msg3_str = "{\"aaa\": [1, 1.1, true, false, \"string\n\" ]}";
- self.msg3_wire = self.msg3_str.encode()
-
- # Due to the inherent impreciseness of floating point values,
- # we test this one separately (with AlmostEqual)
- self.msg_float = 1.1
- self.msg_float_str = "1.1";
- self.msg_float_wire = self.msg_float_str.encode()
-
- def test_encode_json(self):
- self.assertEqual(self.msg1_wire, isc.cc.message.to_wire(self.msg1))
- self.assertEqual(self.msg2_wire, isc.cc.message.to_wire(self.msg2))
- self.assertAlmostEqual(float(self.msg_float_wire),
- float(isc.cc.message.to_wire(self.msg_float)))
- self.assertRaises(TypeError, isc.cc.message.to_wire, NotImplemented)
-
- def test_decode_json(self):
- self.assertEqual(self.msg1, isc.cc.message.from_wire(self.msg1_wire))
- self.assertEqual(self.msg2, isc.cc.message.from_wire(self.msg2_wire))
- self.assertEqual(self.msg3, isc.cc.message.from_wire(self.msg3_wire))
-
- self.assertRaises(AttributeError, isc.cc.message.from_wire, 1)
- self.assertRaises(ValueError, isc.cc.message.from_wire, b'\x001')
- self.assertRaises(ValueError, isc.cc.message.from_wire, b'')
- self.assertRaises(ValueError, isc.cc.message.from_wire, b'{"a": ')
- self.assertRaises(ValueError, isc.cc.message.from_wire, b'[ 1 ')
- self.assertRaises(ValueError, isc.cc.message.from_wire, b']')
-
-if __name__ == '__main__':
- unittest.main()
-
-
+++ /dev/null
-#!/usr/bin/python3
-
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import isc, sys
-
-cc = isc.cc.Session()
-if len(sys.argv) < 3:
- sys.stderr.write('Usage: ' + sys.argv[0] + ' <channel> <command> [arg]\n')
- sys.exit(1)
-
-channel = sys.argv[1]
-command = sys.argv[2]
-if len(sys.argv) >= 4:
- argument = sys.argv[3]
-else:
- argument = ""
-
-cmd = { "command": [ command, argument ] }
-
-cc.group_subscribe(channel)
-print("Sending:")
-print(cmd)
-cc.group_sendmsg(cmd, channel)
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Tests for the ConfigData and MultiConfigData classes
-#
-
-import unittest
-import os
-import json
-from isc.cc.session import *
-
-# our fake socket, where we can read and insert messages
-class MySocket():
- def __init__(self, family, type):
- self.family = family
- self.type = type
- self.recvqueue = bytearray()
- self.sendqueue = bytearray()
- self._blocking = True
- self.send_limit = None
-
- def connect(self, to):
- pass
-
- def close(self):
- pass
-
- def setblocking(self, val):
- self._blocking = val
-
- def send(self, data):
- # If the upper limit is specified, only "send" up to the specified
- # limit
- if self.send_limit is not None and len(data) > self.send_limit:
- self.sendqueue.extend(data[0:self.send_limit])
- return self.send_limit
- else:
- self.sendqueue.extend(data)
- return len(data)
-
- def readsent(self, length):
- if length > len(self.sendqueue):
- raise Exception("readsent(" + str(length) + ") called, but only " + str(len(self.sendqueue)) + " in queue")
- result = self.sendqueue[:length]
- del self.sendqueue[:length]
- return result
-
- def readsentmsg(self):
- """return bytearray of the full message include length specifiers"""
- result = bytearray()
-
- length_buf = self.readsent(4)
- result.extend(length_buf)
- length = struct.unpack('>I', length_buf)[0]
-
- header_length_buf = self.readsent(2)
- header_length = struct.unpack('>H', header_length_buf)[0]
- result.extend(header_length_buf)
-
- data_length = length - 2 - header_length
-
- result.extend(self.readsent(header_length))
- result.extend(self.readsent(data_length))
-
- return result
-
- def readsentmsg_parsed(self):
- length_buf = self.readsent(4)
- length = struct.unpack('>I', length_buf)[0]
- header_length_buf = self.readsent(2)
- header_length = struct.unpack('>H', header_length_buf)[0]
- data_length = length - 2 - header_length
-
- env = json.loads(self.readsent(header_length).decode('utf-8'), strict=False)
- if (data_length > 0):
- msg = json.loads(self.readsent(data_length).decode('utf-8'), strict=False)
- else:
- msg = {}
- return (env, msg)
-
- def recv(self, length):
- if len(self.recvqueue) == 0:
- if self._blocking:
- return bytes()
- else:
- raise socket.error(errno.EAGAIN, "Resource temporarily unavailable")
- if length > len(self.recvqueue):
- raise Exception("Buffer underrun in test, does the test provide the right data?")
- result = self.recvqueue[:length]
- del self.recvqueue[:length]
- #print("[XX] returning: " + str(result))
- #print("[XX] queue now: " + str(self.recvqueue))
- return result
-
- def addrecv(self, env, msg = None):
- if type(env) == dict:
- env = isc.cc.message.to_wire(env)
- if type(msg) == dict:
- msg = isc.cc.message.to_wire(msg)
- length = 2 + len(env);
- if msg:
- length += len(msg)
- self.recvqueue.extend(struct.pack("!I", length))
- self.recvqueue.extend(struct.pack("!H", len(env)))
- self.recvqueue.extend(env)
- if msg:
- self.recvqueue.extend(msg)
-
- def settimeout(self, val):
- pass
-
- def gettimeout(self):
- return 0
-
- def set_send_limit(self, limit):
- '''Specify the upper limit of the transmittable data at once.
-
- By default, the send() method of this class "sends" all given data.
- If this method is called and the its parameter is not None,
- subsequent calls to send() will only transmit the specified amount
- of data. This can be used to emulate the situation where send()
- on a real socket object results in partial write.
- '''
- self.send_limit = limit
-
-#
-# We subclass the Session class we're testing here, only
-# to override the __init__() method, which wants a socket,
-# and we need to use our fake socket
-class MySession(Session):
- def __init__(self, port=9912, s=None):
- self._socket = None
- self._socket_timeout = 1
- self._lname = None
- self._recvbuffer = bytearray()
- self._recv_len_size = 0
- self._recv_size = 0
- self._sequence = 1
- self._closed = False
- self._queue = []
- self._lock = threading.RLock()
-
- if s is not None:
- self._socket = s
- else:
- try:
- self._socket = MySocket(socket.AF_INET, socket.SOCK_STREAM)
- self._socket.connect(tuple(['127.0.0.1', port]))
- self._lname = "test_name"
- # testing getlname here isn't useful, code removed
- except socket.error as se:
- raise SessionError(se)
-
-class testSession(unittest.TestCase):
-
- def test_session_close(self):
- sess = MySession()
- self.assertEqual("test_name", sess.lname)
- sess.close()
- self.assertRaises(SessionError, sess.sendmsg, {}, {"hello": "a"})
-
- def test_env_too_large(self):
- sess = MySession()
- largeenv = { "a": "b"*65535 }
- self.assertRaises(ProtocolError, sess.sendmsg, largeenv, {"hello": "a"})
-
- def test_session_sendmsg(self):
- sess = MySession()
- sess.sendmsg({}, {"hello": "a"})
- sent = sess._socket.readsentmsg();
- self.assertEqual(sent, b'\x00\x00\x00\x12\x00\x02{}{"hello": "a"}')
- sess.close()
- self.assertRaises(SessionError, sess.sendmsg, {}, {"hello": "a"})
-
- def test_session_sendmsg2(self):
- sess = MySession()
- sess.sendmsg({'to': 'someone', 'reply': 1}, {"hello": "a"})
- sent = sess._socket.readsentmsg();
- #print(sent)
- #self.assertRaises(SessionError, sess.sendmsg, {}, {"hello": "a"})
-
- def test_session_sendmsg_shortwrite(self):
- sess = MySession()
- # Specify the upper limit of the size that can be transmitted at
- # a single send() call on the faked socket (10 is an arbitrary choice,
- # just reasonably small).
- sess._socket.set_send_limit(10)
- sess.sendmsg({'to': 'someone', 'reply': 1}, {"hello": "a"})
- # The complete message should still have been transmitted in the end.
- sent = sess._socket.readsentmsg();
-
- def recv_and_compare(self, session, bytes, env, msg):
- """Adds bytes to the recvqueue (which will be read by the
- session object, and compare the resultinv env and msg to
- the ones given."""
- session._socket.addrecv(bytes)
- s_env, s_msg = session.recvmsg(False)
- self.assertEqual(env, s_env)
- self.assertEqual(msg, s_msg)
- # clear the recv buffer in case a malformed message left garbage
- # (actually, shouldn't that case provide some error instead of
- # None?)
- session._socket.recvqueue = bytearray()
-
- def test_session_recvmsg(self):
- sess = MySession()
- # {'to': "someone"}, {"hello": "a"}
- #self.recv_and_compare(sess,
- # b'\x00\x00\x00\x1f\x00\x10Skan\x02to(\x07someoneSkan\x05hello(\x01a',
- # {'to': "someone"}, {"hello": "a"})
-
- # 'malformed' messages
- # shouldn't some of these raise exceptions?
- #self.recv_and_compare(sess,
- # b'\x00',
- # None, None)
- #self.recv_and_compare(sess,
- # b'\x00\x00\x00\x10',
- # None, None)
- #self.recv_and_compare(sess,
- # b'\x00\x00\x00\x02\x00\x00',
- # None, None)
- #self.recv_and_compare(sess,
- # b'\x00\x00\x00\x02\x00\x02',
- # None, None)
- #self.recv_and_compare(sess,
- # b'',
- # None, None)
-
- # need to clear
- sess._socket.recvqueue = bytearray()
-
- # 'queueing' system
- # sending message {'to': 'someone', 'reply': 1}, {"hello": "a"}
- #print("sending message {'to': 'someone', 'reply': 1}, {'hello': 'a'}")
- # get no message without asking for a specific sequence number reply
- self.assertFalse(sess.has_queued_msgs())
- sess._socket.addrecv({'to': 'someone', 'reply': 1}, {"hello": "a"})
- env, msg = sess.recvmsg(True)
- self.assertEqual(None, env)
- self.assertTrue(sess.has_queued_msgs())
- env, msg = sess.recvmsg(True, 1)
- self.assertEqual({'to': 'someone', 'reply': 1}, env)
- self.assertEqual({"hello": "a"}, msg)
- self.assertFalse(sess.has_queued_msgs())
-
- # ask for a different sequence number reply (that doesn't exist)
- # then ask for the one that is there
- self.assertFalse(sess.has_queued_msgs())
- sess._socket.addrecv({'to': 'someone', 'reply': 1}, {"hello": "a"})
- env, msg = sess.recvmsg(True, 2)
- self.assertEqual(None, env)
- self.assertEqual(None, msg)
- self.assertTrue(sess.has_queued_msgs())
- env, msg = sess.recvmsg(True, 1)
- self.assertEqual({'to': 'someone', 'reply': 1}, env)
- self.assertEqual({"hello": "a"}, msg)
- self.assertFalse(sess.has_queued_msgs())
-
- # ask for a different sequence number reply (that doesn't exist)
- # then ask for any message
- self.assertFalse(sess.has_queued_msgs())
- sess._socket.addrecv({'to': 'someone', 'reply': 1}, {"hello": "a"})
- env, msg = sess.recvmsg(True, 2)
- self.assertEqual(None, env)
- self.assertEqual(None, msg)
- self.assertTrue(sess.has_queued_msgs())
- env, msg = sess.recvmsg(True, 1)
- self.assertEqual({'to': 'someone', 'reply': 1}, env)
- self.assertEqual({"hello": "a"}, msg)
- self.assertFalse(sess.has_queued_msgs())
-
- #print("sending message {'to': 'someone', 'reply': 1}, {'hello': 'a'}")
-
- # ask for a different sequence number reply (that doesn't exist)
- # send a new message, ask for specific message (get the first)
- # then ask for any message (get the second)
- self.assertFalse(sess.has_queued_msgs())
- sess._socket.addrecv({'to': 'someone', 'reply': 1}, {'hello': 'a'})
- env, msg = sess.recvmsg(True, 2)
- self.assertEqual(None, env)
- self.assertEqual(None, msg)
- self.assertTrue(sess.has_queued_msgs())
- sess._socket.addrecv({'to': 'someone' }, {'hello': 'b'})
- env, msg = sess.recvmsg(True, 1)
- self.assertEqual({'to': 'someone', 'reply': 1 }, env)
- self.assertEqual({"hello": "a"}, msg)
- self.assertFalse(sess.has_queued_msgs())
- env, msg = sess.recvmsg(True)
- self.assertEqual({'to': 'someone'}, env)
- self.assertEqual({"hello": "b"}, msg)
- self.assertFalse(sess.has_queued_msgs())
-
- # send a message, then one with specific reply value
- # ask for that specific message (get the second)
- # then ask for any message (get the first)
- self.assertFalse(sess.has_queued_msgs())
- sess._socket.addrecv({'to': 'someone' }, {'hello': 'b'})
- sess._socket.addrecv({'to': 'someone', 'reply': 1}, {'hello': 'a'})
- env, msg = sess.recvmsg(True, 1)
- self.assertEqual({'to': 'someone', 'reply': 1}, env)
- self.assertEqual({"hello": "a"}, msg)
- self.assertTrue(sess.has_queued_msgs())
- env, msg = sess.recvmsg(True)
- self.assertEqual({'to': 'someone'}, env)
- self.assertEqual({"hello": "b"}, msg)
- self.assertFalse(sess.has_queued_msgs())
-
- def test_recv_bad_msg(self):
- sess = MySession()
- self.assertFalse(sess.has_queued_msgs())
- sess._socket.addrecv({'to': 'someone' }, {'hello': 'b'})
- sess._socket.addrecv({'to': 'someone', 'reply': 1}, {'hello': 'a'})
- # mangle the bytes a bit
- sess._socket.recvqueue[5] = sess._socket.recvqueue[5] - 2
- sess._socket.recvqueue = sess._socket.recvqueue[:-2]
- self.assertRaises(SessionError, sess.recvmsg, True, 1)
-
- def test_next_sequence(self):
- sess = MySession()
- self.assertEqual(sess._sequence, 1)
- self.assertEqual(sess._next_sequence(), 2)
- self.assertEqual(sess._sequence, 2)
- sess._sequence = 56175
- self.assertEqual(sess._sequence, 56175)
- self.assertEqual(sess._next_sequence(), 56176)
- self.assertEqual(sess._sequence, 56176)
-
- def test_group_subscribe(self):
- sess = MySession()
- sess.group_subscribe("mygroup")
- sent = sess._socket.readsentmsg_parsed()
- self.assertEqual(sent, ({"group": "mygroup", "type": "subscribe",
- "instance": "*"}, {}))
-
- sess.group_subscribe("mygroup")
- sent = sess._socket.readsentmsg_parsed()
- self.assertEqual(sent, ({"group": "mygroup", "type": "subscribe",
- "instance": "*"}, {}))
-
- sess.group_subscribe("mygroup", "my_instance")
- sent = sess._socket.readsentmsg_parsed()
- self.assertEqual(sent, ({"group": "mygroup", "type": "subscribe",
- "instance": "my_instance"}, {}))
-
- def test_group_unsubscribe(self):
- sess = MySession()
- sess.group_unsubscribe("mygroup")
- sent = sess._socket.readsentmsg_parsed()
- self.assertEqual(sent, ({"group": "mygroup", "type": "unsubscribe",
- "instance": "*"}, {}))
-
- sess.group_unsubscribe("mygroup")
- sent = sess._socket.readsentmsg_parsed()
- self.assertEqual(sent, ({"group": "mygroup", "type": "unsubscribe",
- "instance": "*"}, {}))
-
- sess.group_unsubscribe("mygroup", "my_instance")
- sent = sess._socket.readsentmsg_parsed()
- self.assertEqual(sent, ({"group": "mygroup", "type": "unsubscribe",
- "instance": "my_instance"}, {}))
-
- def test_group_sendmsg(self):
- sess = MySession()
- self.assertEqual(sess._sequence, 1)
-
- msg = { "hello": "a" }
-
- def check_sent(additional_headers, sequence):
- sent = sess._socket.readsentmsg_parsed()
- headers = dict({"from": "test_name",
- "seq": sequence,
- "to": "*",
- "type": "send"},
- **additional_headers)
- self.assertEqual(sent, (headers, msg))
- self.assertEqual(sess._sequence, sequence)
-
- sess.group_sendmsg(msg, "my_group")
- check_sent({"instance": "*", "group": "my_group",
- "want_answer": False}, 2)
-
- sess.group_sendmsg(msg, "my_group", "my_instance")
- check_sent({"instance": "my_instance", "group": "my_group",
- "want_answer": False}, 3)
-
- sess.group_sendmsg(msg, "your_group", "your_instance")
- check_sent({"instance": "your_instance", "group": "your_group",
- "want_answer": False}, 4)
-
- # Test the optional want_answer parameter
- sess.group_sendmsg(msg, "group", want_answer=True)
- check_sent({"instance": "*", "group": "group", "want_answer": True}, 5)
-
-
- sess.group_sendmsg(msg, "group", want_answer=False)
- check_sent({"instance": "*", "group": "group", "want_answer": False},
- 6)
-
- def test_group_recvmsg(self):
- # must this one do anything except not return messages with
- # no header?
- pass
-
- def test_group_reply(self):
- sess = MySession()
- sess.group_reply({ 'from': 'me', 'group': 'our_group',
- 'instance': 'other_instance', 'seq': 4},
- {"hello": "a"})
- sent = sess._socket.readsentmsg_parsed();
- self.assertEqual(sent, ({"from": "test_name", "seq": 2,
- "to": "me", "instance": "other_instance",
- "reply": 4, "group": "our_group",
- "type": "send"},
- {"hello": "a"}))
-
- sess.group_reply({ 'from': 'me', 'group': 'our_group',
- 'instance': 'other_instance', 'seq': 9},
- {"hello": "a"})
- sent = sess._socket.readsentmsg_parsed();
- self.assertEqual(sent, ({"from": "test_name", "seq": 3,
- "to": "me", "instance": "other_instance",
- "reply": 9, "group": "our_group",
- "type": "send"},
- {"hello": "a"}))
-
- def test_timeout(self):
- if "BIND10_TEST_SOCKET_FILE" not in os.environ:
- self.assertEqual("", "This test can only run if the value BIND10_TEST_SOCKET_FILE is set in the environment")
- TEST_SOCKET_FILE = os.environ["BIND10_TEST_SOCKET_FILE"]
-
- # create a read domain socket to pass into the session
- s1 = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- if os.path.exists(TEST_SOCKET_FILE):
- os.remove(TEST_SOCKET_FILE)
- s1.bind(TEST_SOCKET_FILE)
-
- try:
- s1.listen(1)
-
- s2 = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- s2.connect(TEST_SOCKET_FILE)
- sess = MySession(1, s2)
- # set timeout to 100 msec, so test does not take too long
- sess.set_timeout(100)
- self.assertRaises(SessionTimeout, sess.group_recvmsg, False)
- finally:
- os.remove(TEST_SOCKET_FILE)
-
-if __name__ == "__main__":
- unittest.main()
-
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import isc
-
-import time
-import pprint
-import unittest
-
-#
-# This test requires the MsgQ daemon to be running. We are doing nasty
-# tricks here, and so insert sleeps to give things time to migrate from
-# this process, to the MsgQ, and back to this process.
-#
-
-class TestCCWireEncoding(unittest.TestCase):
- def setUp(self):
- self.s1 = isc.cc.Session()
- self.s2 = isc.cc.Session()
-
- def tearDown(self):
- self.s1.close()
- self.s2.close()
-
- def test_lname(self):
- self.assertTrue(self.s1.lname)
- self.assertTrue(self.s2.lname)
-
- def test_subscribe(self):
- self.s1.group_subscribe("g1", "i1")
- self.s2.group_subscribe("g1", "i1")
- time.sleep(0.5)
- outmsg = { "data" : "foo" }
- self.s1.group_sendmsg(outmsg, "g1", "i1")
- time.sleep(0.5)
- msg, env = self.s2.group_recvmsg()
- self.assertEqual(env["from"], self.s1.lname)
-
- def test_unsubscribe(self):
- self.s1.group_subscribe("g1", "i1")
- self.s2.group_subscribe("g1", "i1")
- time.sleep(0.5)
- self.s2.group_unsubscribe("g1", "i1")
- outmsg = { "data" : "foo" }
- self.s1.group_sendmsg(outmsg, "g1", "i1")
- time.sleep(0.5)
- msg, env = self.s2.group_recvmsg()
- self.assertFalse(env)
-
- def test_directed_recipient(self):
- self.s1.group_subscribe("g1", "i1")
- time.sleep(0.5)
- outmsg = { "data" : "foo" }
- self.s1.group_sendmsg(outmsg, "g4", "i4", self.s2.lname)
- time.sleep(0.5)
- msg, env = self.s2.group_recvmsg()
- self.assertEqual(env["from"], self.s1.lname)
- self.assertEqual(env["to"], self.s2.lname)
- self.assertEqual(env["group"], "g4")
- self.assertEqual(env["instance"], "i4")
-
-if __name__ == '__main__':
- unittest.main()
+++ /dev/null
-SUBDIRS = . tests
-
-python_PYTHON = __init__.py ccsession.py cfgmgr.py config_data.py module_spec.py
-pythondir = $(pyexecdir)/isc/config
-
-BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/cfgmgr_messages.py
-BUILT_SOURCES += $(PYTHON_LOGMSGPKG_DIR)/work/config_messages.py
-nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/cfgmgr_messages.py
-nodist_pylogmessage_PYTHON += $(PYTHON_LOGMSGPKG_DIR)/work/config_messages.py
-pylogmessagedir = $(pyexecdir)/isc/log_messages/
-
-CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/cfgmgr_messages.py
-CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cfgmgr_messages.pyc
-CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/config_messages.py
-CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/config_messages.pyc
-CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/config_messages.pyo
-
-CLEANDIRS = __pycache__
-
-EXTRA_DIST = cfgmgr_messages.mes config_messages.mes
-
-# Define rule to build logging source files from message file
-$(PYTHON_LOGMSGPKG_DIR)/work/cfgmgr_messages.py : cfgmgr_messages.mes
- $(top_builddir)/src/lib/log/compiler/message \
- -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/cfgmgr_messages.mes
-
-$(PYTHON_LOGMSGPKG_DIR)/work/config_messages.py : config_messages.mes
- $(top_builddir)/src/lib/log/compiler/message \
- -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/config_messages.mes
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-from isc.config.ccsession import *
-from isc.config.config_data import *
-from isc.config.module_spec import *
+++ /dev/null
-# Copyright (C) 2009 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Client-side functionality for configuration and commands
-#
-# It keeps a cc-channel session with the configuration manager daemon,
-# and handles configuration updates and direct commands
-
-# modeled after ccsession.h/cc 'protocol' changes here need to be
-# made there as well
-"""Classes and functions for handling configuration and commands
-
- This module provides the ModuleCCSession and UIModuleCCSession
- classes, as well as a set of utility functions to create and parse
- messages related to commands and configuration
-
- Modules should use the ModuleCCSession class to connect to the
- configuration manager, and receive updates and commands from
- other modules.
-
- Configuration user interfaces should use the UIModuleCCSession
- to connect to b10-cmdctl, and receive and send configuration and
- commands through that to the configuration manager.
-"""
-
-from isc.cc import Session
-from isc.cc.proto_defs import *
-from isc.config.config_data import ConfigData, MultiConfigData, BIND10_CONFIG_DATA_VERSION
-import isc.config.module_spec
-import isc
-from isc.util.file import path_search
-import bind10_config
-from isc.log import log_config_update
-import json
-from isc.log_messages.config_messages import *
-
-logger = isc.log.Logger("config")
-
-class ModuleCCSessionError(Exception): pass
-
-class RPCError(ModuleCCSessionError):
- """
- An exception raised by rpc_call in case the remote side reports
- an error. It can be used to distinguish remote errors from protocol errors.
- Also, it holds the code as well as the error message.
- """
- def __init__(self, code, message):
- ModuleCCSessionError.__init__(self, message)
- self.__code = code
-
- def code(self):
- """
- The code as sent over the CC.
- """
- return self.__code
-
-class RPCRecipientMissing(RPCError):
- """
- Special version of the RPCError, for cases the recipient of the call
- isn't connected to the bus. The code is always
- isc.cc.proto_defs.CC_REPLY_NO_RECPT.
- """
- def __init__(self, message):
- RPCError.__init__(self, CC_REPLY_NO_RECPT, message)
-
-def parse_answer(msg):
- """Returns a tuple (rcode, value), where value depends on the
- command that was called. If rcode != 0, value is a string
- containing an error message"""
- if type(msg) != dict:
- raise ModuleCCSessionError("Answer message is not a dict: " + str(msg))
- if CC_PAYLOAD_RESULT not in msg:
- raise ModuleCCSessionError("answer message does not contain 'result' element")
- elif type(msg[CC_PAYLOAD_RESULT]) != list:
- raise ModuleCCSessionError("wrong result type in answer message")
- elif len(msg[CC_PAYLOAD_RESULT]) < 1:
- raise ModuleCCSessionError("empty result list in answer message")
- elif type(msg[CC_PAYLOAD_RESULT][0]) != int:
- raise ModuleCCSessionError("wrong rcode type in answer message")
- else:
- if len(msg[CC_PAYLOAD_RESULT]) > 1:
- if (msg[CC_PAYLOAD_RESULT][0] != CC_REPLY_SUCCESS and
- type(msg[CC_PAYLOAD_RESULT][1]) != str):
- raise ModuleCCSessionError("rcode in answer message is non-zero, value is not a string")
- return msg[CC_PAYLOAD_RESULT][0], msg[CC_PAYLOAD_RESULT][1]
- else:
- return msg[CC_PAYLOAD_RESULT][0], None
-
-def create_answer(rcode, arg = None):
- """Creates an answer packet for config&commands. rcode must be an
- integer. If rcode == 0, arg is an optional value that depends
- on what the command or option was. If rcode != 0, arg must be
- a string containing an error message"""
- if type(rcode) != int:
- raise ModuleCCSessionError("rcode in create_answer() must be an integer")
- if rcode != CC_REPLY_SUCCESS and type(arg) != str:
- raise ModuleCCSessionError("arg in create_answer for rcode != 0 must be a string describing the error")
- if arg != None:
- return { CC_PAYLOAD_RESULT: [ rcode, arg ] }
- else:
- return { CC_PAYLOAD_RESULT: [ rcode ] }
-
-# 'fixed' commands
-"""Fixed names for command and configuration messages"""
-COMMAND_CONFIG_UPDATE = "config_update"
-COMMAND_MODULE_SPECIFICATION_UPDATE = "module_specification_update"
-
-COMMAND_GET_COMMANDS_SPEC = "get_commands_spec"
-COMMAND_GET_STATISTICS_SPEC = "get_statistics_spec"
-COMMAND_GET_CONFIG = "get_config"
-COMMAND_SET_CONFIG = "set_config"
-COMMAND_GET_MODULE_SPEC = "get_module_spec"
-COMMAND_MODULE_SPEC = "module_spec"
-COMMAND_SHUTDOWN = "shutdown"
-COMMAND_MODULE_STOPPING = "stopping"
-
-def parse_command(msg):
- """Parses what may be a command message. If it looks like one,
- the function returns (command, value) where command is a
- string. If it is not, this function returns None, None"""
- if type(msg) == dict and len(msg.items()) == 1:
- cmd, value = msg.popitem()
- if cmd == CC_PAYLOAD_COMMAND and type(value) == list:
- if len(value) == 1 and type(value[0]) == str:
- return value[0], None
- elif len(value) > 1 and type(value[0]) == str:
- return value[0], value[1]
- return None, None
-
-def create_command(command_name, params = None):
- """Creates a module command message with the given command name (as
- specified in the module's specification, and an optional params
- object"""
- # TODO: validate_command with spec
- if type(command_name) != str:
- raise ModuleCCSessionError("command in create_command() not a string")
- cmd = [ command_name ]
- if params:
- cmd.append(params)
- msg = { CC_PAYLOAD_COMMAND: cmd }
- return msg
-
-def default_logconfig_handler(new_config, config_data):
- errors = []
-
- if config_data.get_module_spec().validate_config(False, new_config, errors):
- isc.log.log_config_update(json.dumps(new_config),
- json.dumps(config_data.get_module_spec().get_full_spec()))
- else:
- logger.error(CONFIG_LOG_CONFIG_ERRORS, errors)
-
-class ModuleCCSession(ConfigData):
- """This class maintains a connection to the command channel, as
- well as configuration options for modules. The module provides
- a specification file that contains the module name, configuration
- options, and commands. It also gives the ModuleCCSession two callback
- functions, one to call when there is a direct command to the
- module, and one to update the configuration run-time. These
- callbacks are called when 'check_command' is called on the
- ModuleCCSession"""
-
- def __init__(self, spec_file_name, config_handler, command_handler,
- cc_session=None, handle_logging_config=True,
- socket_file = None):
- """Initialize a ModuleCCSession. This does *NOT* send the
- specification and request the configuration yet. Use start()
- for that once the ModuleCCSession has been initialized.
-
- specfile_name is the path to the specification file.
-
- config_handler and command_handler are callback functions,
- see set_config_handler and set_command_handler for more
- information on their signatures.
-
- cc_session can be used to pass in an existing CCSession,
- if it is None, one will be set up. This is mainly intended
- for testing purposes.
-
- handle_logging_config: if True, the module session will
- automatically handle logging configuration for the module;
- it will read the system-wide Logging configuration and call
- the logger manager to apply it. It will also inform the
- logger manager when the logging configuration gets updated.
- The module does not need to do anything except initializing
- its loggers, and provide log messages. Defaults to true.
-
- socket_file: If cc_session was none, this optional argument
- specifies which socket file to use to connect to msgq. It
- will be overridden by the environment variable
- MSGQ_SOCKET_FILE. If none, and no environment variable is
- set, it will use the system default.
- """
- module_spec = isc.config.module_spec_from_file(spec_file_name)
- ConfigData.__init__(self, module_spec)
-
- self._module_name = module_spec.get_module_name()
-
- self.set_config_handler(config_handler)
- self.set_command_handler(command_handler)
-
- if not cc_session:
- self._session = Session(socket_file)
- else:
- self._session = cc_session
- self._session.group_subscribe(self._module_name, CC_INSTANCE_WILDCARD)
-
- self._remote_module_configs = {}
- self._remote_module_callbacks = {}
-
- self._notification_callbacks = {}
- self._last_notif_id = 0
-
- if handle_logging_config:
- self.add_remote_config(path_search('logging.spec', bind10_config.PLUGIN_PATHS),
- default_logconfig_handler)
-
- def __del__(self):
- # If the CC Session obejct has been closed, it returns
- # immediately.
- if self._session._closed: return
- self._session.group_unsubscribe(self._module_name,
- CC_INSTANCE_WILDCARD)
- for module_name in self._remote_module_configs:
- self._session.group_unsubscribe(module_name)
-
- def start(self):
- """Send the specification for this module to the configuration
- manager, and request the current non-default configuration.
- The config_handler will be called with that configuration"""
- self.__send_spec()
- self.__request_config()
-
- def send_stopping(self):
- """Sends a 'stopping' message to the configuration manager. This
- message is just an FYI, and no response is expected. Any errors
- when sending this message (for instance if the msgq session has
- previously been closed) are logged, but ignored."""
- # create_command could raise an exception as well, but except for
- # out of memory related errors, these should all be programming
- # failures and are not caught
- msg = create_command(COMMAND_MODULE_STOPPING,
- self.get_module_spec().get_full_spec())
- try:
- self._session.group_sendmsg(msg, "ConfigManager")
- except Exception as se:
- # If the session was previously closed, obvously trying to send
- # a message fails. (TODO: check if session is open so we can
- # error on real problems?)
- logger.error(CONFIG_SESSION_STOPPING_FAILED, se)
-
- def get_socket(self):
- """Returns the socket from the command channel session. This
- should *only* be used for select() loops to see if there
- is anything on the channel. If that loop is not completely
- time-critical, it is strongly recommended to only use
- check_command(), and not look at the socket at all."""
- return self._session._socket
-
- def close(self):
- """Close the session to the command channel"""
- self._session.close()
-
- def check_command(self, nonblock=True):
- """Check whether there is a command or configuration update on
- the channel. This function does a read on the cc session, and
- returns nothing.
- It calls check_command_without_recvmsg()
- to parse the received message.
-
- If nonblock is True, it just checks if there's a command
- and does nothing if there isn't. If nonblock is False, it
- waits until it arrives. It temporarily sets timeout to infinity,
- because commands may not come in arbitrary long time."""
- timeout_orig = self._session.get_timeout()
- self._session.set_timeout(0)
- try:
- msg, env = self._session.group_recvmsg(nonblock)
- finally:
- self._session.set_timeout(timeout_orig)
- self.check_command_without_recvmsg(msg, env)
-
- def check_command_without_recvmsg(self, msg, env):
- """Parse the given message to see if there is a command or a
- configuration update. Calls the corresponding handler
- functions if present. Responds on the channel if the
- handler returns a message."""
- if msg is None:
- return
- if CC_PAYLOAD_NOTIFICATION in msg:
- group_s = env[CC_HEADER_GROUP].split('/', 1)
- # What to do with these bogus inputs? We just ignore them for now.
- if len(group_s) != 2:
- return
- [prefix, group] = group_s
- if prefix + '/' != CC_GROUP_NOTIFICATION_PREFIX:
- return
- # Now, get the callbacks and call one by one
- callbacks = self._notification_callbacks.get(group, {})
- event = msg[CC_PAYLOAD_NOTIFICATION][0]
- params = None
- if len(msg[CC_PAYLOAD_NOTIFICATION]) > 1:
- params = msg[CC_PAYLOAD_NOTIFICATION][1]
- for key in sorted(callbacks.keys()):
- callbacks[key](event, params)
- elif not CC_PAYLOAD_RESULT in msg:
- # should we default to an answer? success-by-default? unhandled
- # error?
- answer = None
- try:
- module_name = env[CC_HEADER_GROUP]
- cmd, arg = isc.config.ccsession.parse_command(msg)
- if cmd == COMMAND_CONFIG_UPDATE:
- new_config = arg
- # If the target channel was not this module
- # it might be in the remote_module_configs
- if module_name != self._module_name:
- if module_name in self._remote_module_configs:
- # no checking for validity, that's up to the
- # module itself.
- newc = self._remote_module_configs[module_name].get_local_config()
- isc.cc.data.merge(newc, new_config)
- self._remote_module_configs[module_name].set_local_config(newc)
- if self._remote_module_callbacks[module_name] != None:
- self._remote_module_callbacks[module_name](new_config,
- self._remote_module_configs[module_name])
- # For other modules, we're not supposed to answer
- return
-
- # ok, so apparently this update is for us.
- errors = []
- if not self._config_handler:
- answer = create_answer(2, self._module_name + " has no config handler")
- elif not self.get_module_spec().validate_config(False, new_config, errors):
- answer = create_answer(1, ", ".join(errors))
- else:
- isc.cc.data.remove_identical(new_config, self.get_local_config())
- answer = self._config_handler(new_config)
- rcode, val = parse_answer(answer)
- if rcode == CC_REPLY_SUCCESS:
- newc = self.get_local_config()
- isc.cc.data.merge(newc, new_config)
- self.set_local_config(newc)
- else:
- # ignore commands for 'remote' modules
- if module_name == self._module_name:
- if self._command_handler:
- answer = self._command_handler(cmd, arg)
- else:
- answer = create_answer(2, self._module_name + " has no command handler")
- except Exception as exc:
- answer = create_answer(1, str(exc))
- if answer:
- self._session.group_reply(env, answer)
-
- def set_config_handler(self, config_handler):
- """Set the config handler for this module. The handler is a
- function that takes the full configuration and handles it.
- It should return an answer created with create_answer()"""
- self._config_handler = config_handler
- # should we run this right now since we've changed the handler?
-
- def set_command_handler(self, command_handler):
- """Set the command handler for this module. The handler is a
- function that takes a command as defined in the .spec file
- and return an answer created with create_answer()"""
- self._command_handler = command_handler
-
- def _add_remote_config_internal(self, module_spec,
- config_update_callback=None):
- """The guts of add_remote_config and add_remote_config_by_name"""
- module_cfg = ConfigData(module_spec)
- module_name = module_spec.get_module_name()
-
- self._session.group_subscribe(module_name)
-
- # Get the current config for that module now
- seq = self._session.group_sendmsg(create_command(COMMAND_GET_CONFIG, { "module_name": module_name }), "ConfigManager")
-
- try:
- answer, _ = self._session.group_recvmsg(False, seq)
- except isc.cc.SessionTimeout:
- raise ModuleCCSessionError("No answer from ConfigManager when "
- "asking about Remote module " +
- module_name)
- call_callback = False
- if answer:
- rcode, value = parse_answer(answer)
- if rcode == 0:
- if value != None:
- if module_spec.validate_config(False, value):
- module_cfg.set_local_config(value)
- call_callback = True
- else:
- raise ModuleCCSessionError("Bad config data for " +
- module_name + ": " +
- str(value))
- else:
- raise ModuleCCSessionError("Failure requesting remote " +
- "configuration data for " +
- module_name)
-
- # all done, add it
- self._remote_module_configs[module_name] = module_cfg
- self._remote_module_callbacks[module_name] = config_update_callback
- if call_callback and config_update_callback is not None:
- config_update_callback(value, module_cfg)
-
- def add_remote_config_by_name(self, module_name,
- config_update_callback=None):
- """
- This does the same as add_remote_config, but you provide the module name
- instead of the name of the spec file.
- """
- seq = self._session.group_sendmsg(create_command(COMMAND_GET_MODULE_SPEC,
- { "module_name":
- module_name }),
- "ConfigManager")
- try:
- answer, env = self._session.group_recvmsg(False, seq)
- except isc.cc.SessionTimeout:
- raise ModuleCCSessionError("No answer from ConfigManager when " +
- "asking about for spec of Remote " +
- "module " + module_name)
- if answer:
- rcode, value = parse_answer(answer)
- if rcode == 0:
- module_spec = isc.config.module_spec.ModuleSpec(value)
- if module_spec.get_module_name() != module_name:
- raise ModuleCCSessionError("Module name mismatch: " +
- module_name + " and " +
- module_spec.get_module_name())
- self._add_remote_config_internal(module_spec,
- config_update_callback)
- else:
- raise ModuleCCSessionError("Error code " + str(rcode) +
- "when asking for module spec of " +
- module_name)
- else:
- raise ModuleCCSessionError("No answer when asking for module " +
- "spec of " + module_name)
- # Just to be consistent with the add_remote_config
- return module_name
-
- def add_remote_config(self, spec_file_name, config_update_callback=None):
- """Gives access to the configuration of a different module.
- These remote module options can at this moment only be
- accessed through get_remote_config_value(). This function
- also subscribes to the channel of the remote module name
- to receive the relevant updates. It is not possible to
- specify your own handler for this right now, but you can
- specify a callback that is called after the change happened.
- start() must have been called on this CCSession
- prior to the call to this method.
- Returns the name of the module."""
- module_spec = isc.config.module_spec_from_file(spec_file_name)
- self._add_remote_config_internal(module_spec, config_update_callback)
- return module_spec.get_module_name()
-
- def remove_remote_config(self, module_name):
- """Removes the remote configuration access for this module"""
- if module_name in self._remote_module_configs:
- self._session.group_unsubscribe(module_name)
- del self._remote_module_configs[module_name]
- del self._remote_module_callbacks[module_name]
-
- def get_remote_config_value(self, module_name, identifier):
- """Returns the current setting for the given identifier at the
- given module. If the module has not been added with
- add_remote_config, a ModuleCCSessionError is raised"""
- if module_name in self._remote_module_configs:
- return self._remote_module_configs[module_name].get_value(identifier)
- else:
- raise ModuleCCSessionError("Remote module " + module_name +
- " not found")
-
- def __send_spec(self):
- """Sends the data specification to the configuration manager"""
- msg = create_command(COMMAND_MODULE_SPEC, self.get_module_spec().get_full_spec())
- seq = self._session.group_sendmsg(msg, "ConfigManager")
- try:
- answer, env = self._session.group_recvmsg(False, seq)
- except isc.cc.SessionTimeout:
- # TODO: log an error?
- pass
-
- def __request_config(self):
- """Asks the configuration manager for the current configuration, and call the config handler if set.
- Raises a ModuleCCSessionError if there is no answer from the configuration manager"""
- seq = self._session.group_sendmsg(create_command(COMMAND_GET_CONFIG, { "module_name": self._module_name }), "ConfigManager")
- try:
- answer, env = self._session.group_recvmsg(False, seq)
- if answer:
- rcode, value = parse_answer(answer)
- if rcode == 0:
- errors = []
- if value != None:
- if self.get_module_spec().validate_config(False,
- value,
- errors):
- self.set_local_config(value)
- if self._config_handler:
- self._config_handler(value)
- else:
- raise ModuleCCSessionError(
- "Wrong data in configuration: " +
- " ".join(errors))
- else:
- logger.error(CONFIG_GET_FAILED, value)
- else:
- raise ModuleCCSessionError("No answer from configuration manager")
- except isc.cc.SessionTimeout:
- raise ModuleCCSessionError("CC Session timeout waiting for configuration manager")
-
- def rpc_call(self, command, group, instance=CC_INSTANCE_WILDCARD,
- to=CC_TO_WILDCARD, params=None):
- """
- Create a command with the given name and parameters. Send it to a
- recipient, wait for the answer and parse it.
-
- This is a wrapper around the group_sendmsg and group_recvmsg on the CC
- session. It exists mostly for convenience.
-
- Params:
- - command: Name of the command to call on the remote side.
- - group, instance, to: Address specification of the recipient.
- - params: Parameters to pass to the command (as keyword arguments).
-
- Return: The return value of the remote call (just the value, no status
- code or anything). May be None.
-
- Raise:
- - RPCRecipientMissing if the given recipient doesn't exist.
- - RPCError if the other side sent an error response. The error string
- is in the exception.
- - ModuleCCSessionError in case of protocol errors, like malformed
- answer.
- """
- cmd = create_command(command, params)
- seq = self._session.group_sendmsg(cmd, group, instance=instance,
- to=to, want_answer=True)
- # For non-blocking, we'll have rpc_call_async (once the nonblock
- # actually works)
- reply, rheaders = self._session.group_recvmsg(nonblock=False, seq=seq)
- code, value = parse_answer(reply)
- if code == CC_REPLY_NO_RECPT:
- raise RPCRecipientMissing(value)
- elif code != CC_REPLY_SUCCESS:
- raise RPCError(code, value)
- return value
-
- def notify(self, notification_group, event_name, params=None):
- """
- Send a notification to subscribed users.
-
- Send a notification message to all users subscribed to the given
- notification group.
-
- This method does not block.
-
- See docs/design/ipc-high.txt for details about notifications
- and the format of messages sent.
-
- Throws:
- - CCSessionError: for low-level communication errors.
- Params:
- - notification_group (string): This parameter (indirectly) signifies
- what users should receive the notification. Only users that
- subscribed to notifications on the same group receive it.
- - event_name (string): The name of the event to notify about (for
- example `new_group_member`).
- - params: Other parameters that describe the event. This might be, for
- example, the ID of the new member and the name of the group. This can
- be any data that can be sent over the isc.cc.Session, but it is
- common for it to be dict.
- Returns: Nothing
- """
- notification = [event_name]
- if params is not None:
- notification.append(params)
- self._session.group_sendmsg({CC_PAYLOAD_NOTIFICATION: notification},
- CC_GROUP_NOTIFICATION_PREFIX +
- notification_group,
- instance=CC_INSTANCE_WILDCARD,
- to=CC_TO_WILDCARD,
- want_answer=False)
-
- def subscribe_notification(self, notification_group, callback):
- """
- Subscribe to receive notifications in given notification group. When a
- notification comes to the group, the callback is called with two
- parameters, the name of the event (the value of `event_name` parameter
- passed to `notify`) and the parameters of the event (the value
- of `params` passed to `notify`).
-
- This is a fast operation (there may be communication with the message
- queue daemon, but it does not wait for any remote process).
-
- The callback may get called multiple times (once for each notification).
- It is possible to subscribe multiple callbacks for the same notification,
- by multiple calls of this method, and they will be called in the order
- of registration when the notification comes.
-
- Throws:
- - CCSessionError: for low-level communication errors.
- Params:
- - notification_group (string): Notification group to subscribe to.
- Notification with the same value of the same parameter of `notify`
- will be received.
- - callback (callable): The callback to be called whenever the
- notification comes.
-
- The callback should not raise exceptions, such exceptions are
- likely to propagate through the loop and terminate program.
- Returns: Opaque id of the subscription. It can be used to cancel
- the subscription by unsubscribe_notification.
- """
- self._last_notif_id += 1
- my_id = self._last_notif_id
- if notification_group in self._notification_callbacks:
- self._notification_callbacks[notification_group][my_id] = callback
- else:
- self._session.group_subscribe(CC_GROUP_NOTIFICATION_PREFIX +
- notification_group)
- self._notification_callbacks[notification_group] = \
- { my_id: callback }
- return (notification_group, my_id)
-
- def unsubscribe_notification(self, nid):
- """
- Remove previous subscription for notifications. Pass the id returned
- from subscribe_notification.
-
- Throws:
- - CCSessionError: for low-level communication errors.
- - KeyError: The id does not correspond to valid subscription.
- """
- (group, cid) = nid
- del self._notification_callbacks[group][cid]
- if not self._notification_callbacks[group]:
- # Removed the last one
- self._session.group_unsubscribe(CC_GROUP_NOTIFICATION_PREFIX +
- group)
- del self._notification_callbacks[group]
-
-class UIModuleCCSession(MultiConfigData):
- """This class is used in a configuration user interface. It contains
- specific functions for getting, displaying, and sending
- configuration settings through the b10-cmdctl module."""
- def __init__(self, conn):
- """Initialize a UIModuleCCSession. The conn object that is
- passed must have send_GET and send_POST functions"""
- MultiConfigData.__init__(self)
- self._conn = conn
- self.update_specs_and_config()
-
- def request_specifications(self):
- """Clears the current list of specifications, and requests a new
- list from b10-cmdctl. As other actions may have caused modules
- to be stopped, or new modules to be added, this is expected to
- be run after each interaction (at this moment). It is usually
- also combined with request_current_config(). For that reason,
- we provide update_specs_and_config() which calls both."""
- specs = self._conn.send_GET('/module_spec')
- self.clear_specifications()
- for module in specs.keys():
- self.set_specification(isc.config.ModuleSpec(specs[module]))
-
- def request_current_config(self):
- """Requests the current configuration from the configuration
- manager through b10-cmdctl, and stores those as CURRENT. This
- does not modify any local changes, it just updates to the current
- state of the server itself."""
- config = self._conn.send_GET('/config_data')
- if 'version' not in config or config['version'] != BIND10_CONFIG_DATA_VERSION:
- raise ModuleCCSessionError("Bad config version")
- self._set_current_config(config)
-
- def update_specs_and_config(self):
- """Convenience function to both clear and update the known list of
- module specifications, and update the current configuration on
- the server side. There are a few cases where the caller might only
- want to run one of these tasks, but often they are both needed."""
- self.request_specifications()
- self.request_current_config()
-
- def _add_value_to_list(self, identifier, value, module_spec):
- cur_list, status = self.get_value(identifier)
- if not cur_list:
- cur_list = []
-
- if value is None and "list_item_spec" in module_spec:
- if "item_default" in module_spec["list_item_spec"]:
- value = module_spec["list_item_spec"]["item_default"]
-
- if value is None:
- raise isc.cc.data.DataNotFoundError(
- "No value given and no default for " + str(identifier))
-
- if value not in cur_list:
- cur_list.append(value)
- self.set_value(identifier, cur_list)
- else:
- raise isc.cc.data.DataAlreadyPresentError(str(value) +
- " already in "
- + str(identifier))
-
- def _add_value_to_named_set(self, identifier, value, item_value):
- if type(value) != str:
- raise isc.cc.data.DataTypeError("Name for named_set " +
- identifier +
- " must be a string")
- # fail on both None and empty string
- if not value:
- raise isc.cc.data.DataNotFoundError(
- "Need a name to add a new item to named_set " +
- str(identifier))
- else:
- cur_map, status = self.get_value(identifier)
- if not cur_map:
- cur_map = {}
- if value not in cur_map:
- cur_map[value] = item_value
- self.set_value(identifier, cur_map)
- else:
- raise isc.cc.data.DataAlreadyPresentError(value +
- " already in " +
- identifier)
-
- def add_value(self, identifier, value_str = None, set_value_str = None):
- """Add a value to a configuration list. Raises a DataTypeError
- if the value does not conform to the list_item_spec field
- of the module config data specification. If value_str is
- not given, we add the default as specified by the .spec
- file. Raises a DataNotFoundError if the given identifier
- is not specified in the specification as a map or list.
- Raises a DataAlreadyPresentError if the specified element
- already exists."""
- module_spec = self.find_spec_part(identifier)
- if module_spec is None:
- raise isc.cc.data.DataNotFoundError("Unknown item " + str(identifier))
-
- # for type any, we determine the 'type' by what value is set
- # (which would be either list or dict)
- cur_value, _ = self.get_value(identifier)
- type_any = isc.config.config_data.spec_part_is_any(module_spec)
-
- # the specified element must be a list or a named_set
- if 'list_item_spec' in module_spec or\
- (type_any and type(cur_value) == list):
- value = None
- # in lists, we might get the value with spaces, making it
- # the third argument. In that case we interpret both as
- # one big string meant as the value
- if value_str is not None:
- if set_value_str is not None:
- value_str += set_value_str
- value = isc.cc.data.parse_value_str(value_str)
- self._add_value_to_list(identifier, value, module_spec)
- elif 'named_set_item_spec' in module_spec or\
- (type_any and type(cur_value) == dict):
- item_name = None
- item_value = None
- if value_str is not None:
- item_name = value_str
- if set_value_str is not None:
- item_value = isc.cc.data.parse_value_str(set_value_str)
- else:
- if 'item_default' in module_spec['named_set_item_spec']:
- item_value = module_spec['named_set_item_spec']['item_default']
- self._add_value_to_named_set(identifier, item_name,
- item_value)
- else:
- raise isc.cc.data.DataTypeError(str(identifier) + " is not a list or a named set")
-
- def _remove_value_from_list(self, identifier, value):
- if value is None:
- # we are directly removing a list index
- id, list_indices = isc.cc.data.split_identifier_list_indices(identifier)
- if list_indices is None:
- raise isc.cc.data.DataTypeError("identifier in remove_value() does not contain a list index, and no value to remove")
- else:
- self.set_value(identifier, None)
- else:
- cur_list, status = self.get_value(identifier)
- if not cur_list:
- cur_list = []
- elif value in cur_list:
- cur_list.remove(value)
- self.set_value(identifier, cur_list)
-
- def _remove_value_from_named_set(self, identifier, value):
- if value is None:
- raise isc.cc.data.DataNotFoundError("Need a name to remove an item from named_set " + str(identifier))
- elif type(value) != str:
- raise isc.cc.data.DataTypeError("Name for named_set " + identifier + " must be a string")
- else:
- cur_map, status = self.get_value(identifier)
- if not cur_map:
- cur_map = {}
- if value in cur_map:
- del cur_map[value]
- self.set_value(identifier, cur_map)
- else:
- raise isc.cc.data.DataNotFoundError(value + " not found in named_set " + str(identifier))
-
- def remove_value(self, identifier, value_str):
- """Remove a value from a configuration list or named set.
- The value string must be a string representation of the full
- item. Raises a DataTypeError if the value at the identifier
- is not a list, or if the given value_str does not match the
- list_item_spec """
- module_spec = self.find_spec_part(identifier)
- if module_spec is None:
- raise isc.cc.data.DataNotFoundError("Unknown item " + str(identifier))
-
- value = None
- if value_str is not None:
- value = isc.cc.data.parse_value_str(value_str)
-
- # for type any, we determine the 'type' by what value is set
- # (which would be either list or dict)
- cur_value, _ = self.get_value(identifier)
- type_any = isc.config.config_data.spec_part_is_any(module_spec)
-
- # there's two forms of 'remove from list'; the remove-value-from-list
- # form, and the 'remove-by-index' form. We can recognize the second
- # case by value is None
- if 'list_item_spec' in module_spec or\
- (type_any and type(cur_value) == list) or\
- value is None:
- if not type_any and value is not None:
- isc.config.config_data.check_type(module_spec['list_item_spec'], value)
- self._remove_value_from_list(identifier, value)
- elif 'named_set_item_spec' in module_spec or\
- (type_any and type(cur_value) == dict):
- self._remove_value_from_named_set(identifier, value_str)
- else:
- raise isc.cc.data.DataTypeError(str(identifier) + " is not a list or a named_set")
-
-
-
- def commit(self):
- """Commit all local changes, send them through b10-cmdctl to
- the configuration manager"""
- if self.get_local_changes():
- response = self._conn.send_POST('/ConfigManager/set_config',
- [ self.get_local_changes() ])
- answer = isc.cc.data.parse_value_str(response.read().decode())
- # answer is either an empty dict (on success), or one
- # containing errors
- if answer == {}:
- self.clear_local_changes()
- elif "error" in answer:
- raise ModuleCCSessionError("Error: " + str(answer["error"]) + "\n" + "Configuration not committed")
- else:
- raise ModuleCCSessionError("Unknown format of answer in commit(): " + str(answer))
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""This is the BIND 10 configuration manager, run by b10-cfgmgr.
-
- It stores the system configuration, and sends updates of the
- configuration to the modules that need them.
-"""
-
-import isc
-import signal
-import ast
-import os
-import copy
-import tempfile
-import json
-import errno
-from isc.cc import data
-from isc.cc.proto_defs import *
-from isc.config import ccsession, config_data, module_spec
-from isc.util.file import path_search
-import bind10_config
-import isc.log
-from isc.log_messages.cfgmgr_messages import *
-
-logger = isc.log.Logger("cfgmgr", buffer=True)
-
-class ConfigManagerDataReadError(Exception):
- """This exception is thrown when there is an error while reading
- the current configuration on startup."""
- pass
-
-class ConfigManagerDataEmpty(Exception):
- """This exception is thrown when the currently stored configuration
- is not found, or appears empty."""
- pass
-
-class ConfigManagerData:
- """This class hold the actual configuration information, and
- reads it from and writes it to persistent storage"""
-
- def __init__(self, data_path, file_name):
- """Initialize the data for the configuration manager, and
- set the version and path for the data store. Initializing
- this does not yet read the database, a call to
- read_from_file is needed for that.
-
- In case the file_name is absolute, data_path is ignored
- and the directory where the file_name lives is used instead.
- """
- self.data = {}
- self.data['version'] = config_data.BIND10_CONFIG_DATA_VERSION
- if os.path.isabs(file_name):
- self.db_filename = file_name
- self.data_path = os.path.dirname(file_name)
- else:
- self.db_filename = data_path + os.sep + file_name
- self.data_path = data_path
-
- def check_for_updates(file_config):
- """
- Given the parsed JSON data from the config file,
- check whether it needs updating due to version changes.
- Return the data with updates (or the original data if no
- updates were necessary).
- Even though it is at this moment not technically necessary, this
- function makes and returns a copy of the given data.
- """
- config = copy.deepcopy(file_config)
- if 'version' in config:
- data_version = config['version']
- else:
- # If it is not present, assume latest or earliest?
- data_version = 1
-
- # For efficiency, if up-to-date, return now
- if data_version == config_data.BIND10_CONFIG_DATA_VERSION:
- return config
-
- # Don't know what to do if it is more recent
- if data_version > config_data.BIND10_CONFIG_DATA_VERSION:
- raise ConfigManagerDataReadError(
- "Cannot load configuration file: version "
- "%d not yet supported" % config['version'])
-
- # At some point we might give up supporting older versions
- if data_version < 1:
- raise ConfigManagerDataReadError(
- "Cannot load configuration file: version "
- "%d no longer supported" % config['version'])
-
- # Ok, so we have a still-supported older version. Apply all
- # updates
- new_data_version = data_version
- if new_data_version == 1:
- # only format change, no other changes necessary
- new_data_version = 2
- if new_data_version == 2:
- # 'Boss' got changed to 'Init'; If for some reason both are
- # present, simply ignore the old one
- if 'Boss' in config:
- if not 'Init' in config:
- config['Init'] = config['Boss']
- del config['Boss']
- else:
- # This should not happen, but we don't want to overwrite
- # any config in this case, so warn about it
- logger.warn(CFGMGR_CONFIG_UPDATE_BOSS_AND_INIT_FOUND)
- new_data_version = 3
-
- config['version'] = new_data_version
- logger.info(CFGMGR_AUTOMATIC_CONFIG_DATABASE_UPDATE, data_version,
- new_data_version)
- return config
-
- def read_from_file(data_path, file_name):
- """Read the current configuration found in the file file_name.
- If file_name is absolute, data_path is ignored. Otherwise
- we look for the file_name in data_path directory.
-
- If the file does not exist, a ConfigManagerDataEmpty exception is
- raised. If there is a parse error, or if the data in the file has
- the wrong version, a ConfigManagerDataReadError is raised. In the
- first case, it is probably safe to log and ignore. In the case of
- the second exception, the best way is probably to report the error
- and stop loading the system.
- """
- config = ConfigManagerData(data_path, file_name)
- logger.info(CFGMGR_CONFIG_FILE, config.db_filename)
- file = None
- try:
- file = open(config.db_filename, 'r')
- file_config = json.loads(file.read())
- # handle different versions here
- # If possible, we automatically convert to the new
- # scheme and update the configuration
- # If not, we raise an exception
- config.data = ConfigManagerData.check_for_updates(file_config)
- except IOError as ioe:
- # if IOError is 'no such file or directory', then continue
- # (raise empty), otherwise fail (raise error)
- if ioe.errno == errno.ENOENT:
- raise ConfigManagerDataEmpty("No configuration file found")
- else:
- raise ConfigManagerDataReadError("Can't read configuration file: " + str(ioe))
- except ValueError:
- raise ConfigManagerDataReadError("Configuration file out of date or corrupt, please update or remove " + config.db_filename)
- finally:
- if file:
- file.close();
- return config
-
- def write_to_file(self, output_file_name = None):
- """Writes the current configuration data to a file. If
- output_file_name is not specified, the file used in
- read_from_file is used."""
- filename = None
-
- try:
- file = tempfile.NamedTemporaryFile(mode='w',
- prefix="b10-config.db.",
- dir=self.data_path,
- delete=False)
- filename = file.name
- file.write(json.dumps(self.data))
- file.write("\n")
- file.close()
- if output_file_name:
- os.rename(filename, output_file_name)
- else:
- os.rename(filename, self.db_filename)
- except IOError as ioe:
- logger.error(CFGMGR_IOERROR_WHILE_WRITING_CONFIGURATION, ioe)
- except OSError as ose:
- logger.error(CFGMGR_OSERROR_WHILE_WRITING_CONFIGURATION, ose)
- try:
- if filename and os.path.exists(filename):
- os.remove(filename)
- except OSError:
- # Ok if we really can't delete it anymore, leave it
- pass
-
- def rename_config_file(self, old_file_name=None, new_file_name=None):
- """Renames the given configuration file to the given new file name,
- if it exists. If it does not exist, nothing happens.
- If old_file_name is None (default), the file used in
- read_from_file is used. If new_file_name is None (default), the
- file old_file_name appended with .bak is used. If that file exists
- already, .1 is appended. If that file exists, .2 is appended, etc.
- """
- if old_file_name is None:
- old_file_name = self.db_filename
- if new_file_name is None:
- new_file_name = old_file_name + ".bak"
- if os.path.exists(new_file_name):
- i = 1
- while os.path.exists(new_file_name + "." + str(i)):
- i += 1
- new_file_name = new_file_name + "." + str(i)
- if os.path.exists(old_file_name):
- logger.info(CFGMGR_BACKED_UP_CONFIG_FILE, old_file_name, new_file_name)
- os.rename(old_file_name, new_file_name)
-
- def __eq__(self, other):
- """Returns True if the data contained is equal. data_path and
- db_filename may be different."""
- if type(other) != type(self):
- return False
- return self.data == other.data
-
-class ConfigManager:
- """Creates a configuration manager. The data_path is the path
- to the directory containing the configuration file,
- database_filename points to the configuration file.
- If session is set, this will be used as the communication
- channel session. If not, a new session will be created.
- The ability to specify a custom session is for testing purposes
- and should not be needed for normal usage."""
- def __init__(self, data_path, database_filename, session=None,
- clear_config=False):
- """Initialize the configuration manager. The data_path string
- is the path to the directory where the configuration is
- stored (in <data_path>/<database_filename> or in
- <database_filename>, if it is absolute). The database_filename
- is the config file to load. Session is an optional
- cc-channel session. If this is not given, a new one is
- created. If clear_config is True, the configuration file is
- renamed and a new one is created."""
- self.data_path = data_path
- self.database_filename = database_filename
- self.module_specs = {}
- # Virtual modules are the ones which have no process running. The
- # checking of validity is done by functions presented here instead
- # of some other process
- self.virtual_modules = {}
- self.config = ConfigManagerData(data_path, database_filename)
- if clear_config:
- self.config.rename_config_file()
- if session:
- self.cc = session
- else:
- self.cc = isc.cc.Session()
- self.cc.group_subscribe("ConfigManager")
- self.cc.group_subscribe("Init", "ConfigManager")
- self.running = False
- # As a core module, CfgMgr is different than other modules,
- # as it does not use a ModuleCCSession, and hence needs
- # to handle logging config on its own
- self.log_config_data = config_data.ConfigData(
- isc.config.module_spec_from_file(
- path_search('logging.spec',
- bind10_config.PLUGIN_PATHS)))
- # store the logging 'module' name for easier reference
- self.log_module_name = self.log_config_data.get_module_spec().get_module_name()
-
- def check_logging_config(self, config):
- if self.log_module_name in config:
- # If there is logging config, apply it.
- ccsession.default_logconfig_handler(config[self.log_module_name],
- self.log_config_data)
- else:
- # If there is no logging config, we still need to trigger the
- # handler, so make it use defaults (and flush any buffered logs)
- ccsession.default_logconfig_handler({}, self.log_config_data)
-
- def notify_b10_init(self):
- """Notifies the Init module that the Config Manager is running"""
- # TODO: Use a real, broadcast notification here.
- self.cc.group_sendmsg({"running": "ConfigManager"}, "Init")
-
- def set_module_spec(self, spec):
- """Adds a ModuleSpec"""
- self.module_specs[spec.get_module_name()] = spec
-
- def set_virtual_module(self, spec, check_func):
- """Adds a virtual module with its spec and checking function."""
- self.module_specs[spec.get_module_name()] = spec
- self.virtual_modules[spec.get_module_name()] = check_func
-
- def remove_module_spec(self, module_name):
- """Removes the full ModuleSpec for the given module_name.
- Also removes the virtual module check function if it
- was present.
- Does nothing if the module was not present."""
- if module_name in self.module_specs:
- del self.module_specs[module_name]
- if module_name in self.virtual_modules:
- del self.virtual_modules[module_name]
-
- def get_module_spec(self, module_name = None):
- """Returns the full ModuleSpec for the module with the given
- module_name. If no module name is given, a dict will
- be returned with 'name': module_spec values. If the
- module name is given, but does not exist, an empty dict
- is returned"""
- if module_name:
- if module_name in self.module_specs:
- return self.module_specs[module_name].get_full_spec()
- else:
- # TODO: log error?
- return {}
- else:
- result = {}
- for module in self.module_specs:
- result[module] = self.module_specs[module].get_full_spec()
- return result
-
- def get_config_spec(self, name = None):
- """Returns a dict containing 'module_name': config_spec for
- all modules. If name is specified, only that module will
- be included"""
- config_data = {}
- if name:
- if name in self.module_specs:
- config_data[name] = self.module_specs[name].get_config_spec()
- else:
- for module_name in self.module_specs.keys():
- config_data[module_name] = self.module_specs[module_name].get_config_spec()
- return config_data
-
- def get_commands_spec(self, name = None):
- """Returns a dict containing 'module_name': commands_spec for
- all modules. If name is specified, only that module will
- be included"""
- commands = {}
- if name:
- if name in self.module_specs:
- commands[name] = self.module_specs[name].get_commands_spec()
- else:
- for module_name in self.module_specs.keys():
- commands[module_name] = self.module_specs[module_name].get_commands_spec()
- return commands
-
- def get_statistics_spec(self, name = None):
- """Returns a dict containing 'module_name': statistics_spec for
- all modules. If name is specified, only that module will
- be included"""
- statistics = {}
- if name:
- if name in self.module_specs:
- statistics[name] = self.module_specs[name].get_statistics_spec()
- else:
- for module_name in self.module_specs.keys():
- statistics[module_name] = self.module_specs[module_name].get_statistics_spec()
- return statistics
-
- def read_config(self):
- """Read the current configuration from the file specificied at init()"""
- try:
- self.config = ConfigManagerData.read_from_file(self.data_path,
- self.\
- database_filename)
- except ConfigManagerDataEmpty:
- # ok, just start with an empty config
- self.config = ConfigManagerData(self.data_path,
- self.database_filename)
- self.check_logging_config(self.config.data);
-
- def write_config(self):
- """Write the current configuration to the file specificied at init()"""
- self.config.write_to_file()
-
- def __handle_get_module_spec(self, cmd):
- """Private function that handles the 'get_module_spec' command"""
- answer = {}
- if cmd != None:
- if type(cmd) == dict:
- if 'module_name' in cmd and cmd['module_name'] != '':
- module_name = cmd['module_name']
- spec = self.get_module_spec(cmd['module_name'])
- if type(spec) != type({}):
- # this is a ModuleSpec object. Extract the
- # internal spec.
- spec = spec.get_full_spec()
- answer = ccsession.create_answer(0, spec)
- else:
- answer = ccsession.create_answer(1, "Bad module_name in get_module_spec command")
- else:
- answer = ccsession.create_answer(1, "Bad get_module_spec command, argument not a dict")
- else:
- answer = ccsession.create_answer(0, self.get_module_spec())
- return answer
-
- def __handle_get_config_dict(self, cmd):
- """Private function that handles the 'get_config' command
- where the command has been checked to be a dict"""
- if 'module_name' in cmd and cmd['module_name'] != '':
- module_name = cmd['module_name']
- try:
- return ccsession.create_answer(0, data.find(self.config.data, module_name))
- except data.DataNotFoundError as dnfe:
- # no data is ok, that means we have nothing that
- # deviates from default values
- return ccsession.create_answer(0, { 'version': config_data.BIND10_CONFIG_DATA_VERSION })
- else:
- return ccsession.create_answer(1, "Bad module_name in get_config command")
-
- def __handle_get_config(self, cmd):
- """Private function that handles the 'get_config' command"""
- if cmd != None:
- if type(cmd) == dict:
- return self.__handle_get_config_dict(cmd)
- else:
- return ccsession.create_answer(1, "Bad get_config command, argument not a dict")
- else:
- return ccsession.create_answer(0, self.config.data)
-
- def __handle_set_config_module(self, module_name, cmd):
- # the answer comes (or does not come) from the relevant module
- # so we need a variable to see if we got it
- answer = None
- # todo: use api (and check the data against the definition?)
- old_data = copy.deepcopy(self.config.data)
- conf_part = data.find_no_exc(self.config.data, module_name)
- update_cmd = None
- use_part = None
- if conf_part:
- data.merge(conf_part, cmd)
- use_part = conf_part
- else:
- conf_part = data.set(self.config.data, module_name, {})
- data.merge(conf_part[module_name], cmd)
- use_part = conf_part[module_name]
-
- # The command to send
- update_cmd = ccsession.create_command(ccsession.COMMAND_CONFIG_UPDATE,
- use_part)
-
- # TODO: This design might need some revisiting. We might want some
- # polymorphism instead of branching. But it just might turn out it
- # will get solved by itself when we move everything to virtual modules
- # (which is possible solution to the offline configuration problem)
- # or when we solve the incorrect behaviour here when a config is
- # rejected (spying modules don't know it was rejected and some modules
- # might have been committed already).
- if module_name in self.virtual_modules:
- # The module is virtual, so call it to get the answer
- try:
- error = self.virtual_modules[module_name](use_part)
- if error is None:
- answer = ccsession.create_answer(0)
- # OK, it is successful, send the notify, but don't wait
- # for answer
- seq = self.cc.group_sendmsg(update_cmd, module_name)
- else:
- answer = ccsession.create_answer(1, error)
- # Make sure just a validating plugin don't kill the whole manager
- except Exception as excp:
- # Provide answer
- answer = ccsession.create_answer(1, "Exception: " + str(excp))
- else:
- # Real module, send it over the wire to it
- # send out changed info and wait for answer
- seq = self.cc.group_sendmsg(update_cmd, module_name)
- try:
- # replace 'our' answer with that of the module
- answer, env = self.cc.group_recvmsg(False, seq)
- except isc.cc.SessionTimeout:
- answer = ccsession.create_answer(1, "Timeout waiting for answer from " + module_name)
- except isc.cc.SessionError as se:
- logger.error(CFGMGR_BAD_UPDATE_RESPONSE_FROM_MODULE, module_name, se)
- answer = ccsession.create_answer(1, "Unable to parse response from " + module_name + ": " + str(se))
- if answer:
- rcode, val = ccsession.parse_answer(answer)
- if rcode == 0:
- self.write_config()
- else:
- self.config.data = old_data
- return answer
-
- def __handle_set_config_all(self, cmd):
- old_data = copy.deepcopy(self.config.data)
- got_error = False
- err_list = []
- # The format of the command is a dict with module->newconfig
- # sets, so we simply call set_config_module for each of those
- for module in cmd:
- if module != "version":
- answer = self.__handle_set_config_module(module, cmd[module])
- if answer == None:
- got_error = True
- err_list.append("No answer message from " + module)
- else:
- rcode, val = ccsession.parse_answer(answer)
- if rcode != 0:
- got_error = True
- err_list.append(val)
- if not got_error:
- # if Logging config is in there, update our config as well
- self.check_logging_config(cmd);
- self.write_config()
- return ccsession.create_answer(0)
- else:
- # TODO rollback changes that did get through, should we re-send update?
- self.config.data = old_data
- return ccsession.create_answer(1, " ".join(err_list))
-
- def __handle_set_config(self, cmd):
- """Private function that handles the 'set_config' command"""
- answer = None
-
- if cmd == None:
- return ccsession.create_answer(1, "Wrong number of arguments")
- if len(cmd) == 2:
- answer = self.__handle_set_config_module(cmd[0], cmd[1])
- elif len(cmd) == 1:
- answer = self.__handle_set_config_all(cmd[0])
- else:
- answer = ccsession.create_answer(1, "Wrong number of arguments")
- if not answer:
- answer = ccsession.create_answer(1, "No answer message from " + cmd[0])
-
- return answer
-
- def __handle_module_spec(self, spec):
- """Private function that handles the 'module_spec' command"""
- # todo: validate? (no direct access to spec as
- # todo: use ModuleSpec class
- # todo: error checking (like keyerrors)
- answer = {}
- self.set_module_spec(spec)
- self._send_module_spec_to_cmdctl(spec.get_module_name(),
- spec.get_full_spec())
- return ccsession.create_answer(0)
-
- def __handle_module_stopping(self, arg):
- """Private function that handles a 'stopping' command;
- The argument is of the form { 'module_name': <name> }.
- If the module is known, it is removed from the known list,
- and a message is sent to the Cmdctl channel to remove it as well.
- If it is unknown, the message is ignored."""
- if arg['module_name'] in self.module_specs:
- del self.module_specs[arg['module_name']]
- self._send_module_spec_to_cmdctl(arg['module_name'], None)
- # This command is not expected to be answered
- return None
-
- def _send_module_spec_to_cmdctl(self, module_name, spec):
- """Sends the given module spec for the given module name to Cmdctl.
- Parameters:
- module_name: A string with the name of the module
- spec: dict containing full module specification, as returned by
- ModuleSpec.get_full_spec(). This argument may also be None,
- in which case it signals Cmdctl to remove said module from
- its list.
- No response from Cmdctl is expected."""
- spec_update = ccsession.create_command(ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE,
- [ module_name, spec ])
- self.cc.group_sendmsg(spec_update, "Cmdctl")
-
- def handle_msg(self, msg):
- """Handle a command from the cc channel to the configuration manager"""
- answer = {}
- cmd, arg = ccsession.parse_command(msg)
- if cmd:
- if cmd == ccsession.COMMAND_GET_COMMANDS_SPEC:
- answer = ccsession.create_answer(0, self.get_commands_spec())
- elif cmd == ccsession.COMMAND_GET_STATISTICS_SPEC:
- answer = ccsession.create_answer(0, self.get_statistics_spec())
- elif cmd == ccsession.COMMAND_GET_MODULE_SPEC:
- answer = self.__handle_get_module_spec(arg)
- elif cmd == ccsession.COMMAND_GET_CONFIG:
- answer = self.__handle_get_config(arg)
- elif cmd == ccsession.COMMAND_SET_CONFIG:
- answer = self.__handle_set_config(arg)
- elif cmd == ccsession.COMMAND_MODULE_STOPPING:
- answer = self.__handle_module_stopping(arg)
- elif cmd == ccsession.COMMAND_SHUTDOWN:
- self.running = False
- answer = ccsession.create_answer(0)
- elif cmd == ccsession.COMMAND_MODULE_SPEC:
- try:
- answer = self.__handle_module_spec(isc.config.ModuleSpec(arg))
- except isc.config.ModuleSpecError as dde:
- answer = ccsession.create_answer(1, "Error in data definition: " + str(dde))
- else:
- answer = ccsession.create_answer(1, "Unknown command: " + str(cmd))
- else:
- answer = ccsession.create_answer(1, "Unknown message format: " + str(msg))
- return answer
-
- def run(self):
- """Runs the configuration manager."""
- self.running = True
- while self.running:
- # we just wait eternally for any command here, so disable
- # timeouts for this specific recv
- self.cc.set_timeout(0)
- msg, env = self.cc.group_recvmsg(False)
- # and set it back to whatever we default to
- self.cc.set_timeout(isc.cc.Session.MSGQ_DEFAULT_TIMEOUT)
- # ignore 'None' value (even though they should not occur)
- # and messages that are answers to questions we did
- # not ask
- if msg is not None and not CC_PAYLOAD_RESULT in msg:
- answer = self.handle_msg(msg);
- # Only respond if there actually is something to respond with
- if answer is not None:
- self.cc.group_reply(env, answer)
- logger.info(CFGMGR_STOPPED_BY_COMMAND)
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# No namespace declaration - these constants go in the global namespace
-# of the xfrin messages python module.
-
-% CFGMGR_AUTOMATIC_CONFIG_DATABASE_UPDATE Updating configuration database from version %1 to %2
-An older version of the configuration database has been found, from which
-there was an automatic upgrade path to the current version. These changes
-are now applied, and no action from the administrator is necessary.
-
-% CFGMGR_BACKED_UP_CONFIG_FILE Config file %1 was removed; a backup was made at %2
-BIND 10 has been started with the command to clear the configuration
-file. The existing file has been backed up (moved) to the given file
-name. A new configuration file will be created in the original location
-when necessary.
-
-% CFGMGR_BAD_UPDATE_RESPONSE_FROM_MODULE Unable to parse response from module %1: %2
-The configuration manager sent a configuration update to a module, but
-the module responded with an answer that could not be parsed. The answer
-message appears to be invalid JSON data, or not decodable to a string.
-This is likely to be a problem in the module in question. The update is
-assumed to have failed, and will not be stored.
-
-% CFGMGR_CC_SESSION_ERROR Error connecting to command channel: %1
-The configuration manager daemon was unable to connect to the messaging
-system. The most likely cause is that msgq is not running.
-
-% CFGMGR_CONFIG_FILE Configuration manager starting with configuration file: %1
-The configuration manager is starting, reading and saving the configuration
-settings to the shown file.
-
-% CFGMGR_CONFIG_UPDATE_BOSS_AND_INIT_FOUND Configuration found for both 'Boss' and 'Init', ignoring 'Boss'
-In the process of updating the configuration from version 2 to version 3,
-the configuration manager has found that there are existing configurations
-for both the old value 'Boss' and the new value 'Init'. This should in
-theory not happen, as in older versions 'Init' does not exist, and in newer
-versions 'Boss' does not exist. The configuration manager will continue
-with the update process, leaving the values for both as they are, so as not
-to overwrite any settings. However, the values for 'Boss' are ignored by
-BIND 10, and it is probably wise to check the configuration file manually.
-
-% CFGMGR_DATA_READ_ERROR error reading configuration database from disk: %1
-There was a problem reading the persistent configuration data as stored
-on disk. The file may be corrupted, or it is of a version from where
-there is no automatic upgrade path. The file needs to be repaired or
-removed. The configuration manager daemon will now shut down.
-
-% CFGMGR_IOERROR_WHILE_WRITING_CONFIGURATION Unable to write configuration file; configuration not stored: %1
-There was an IO error from the system while the configuration manager
-was trying to write the configuration database to disk. The specific
-error is given. The most likely cause is that the directory where
-the file is stored does not exist, or is not writable. The updated
-configuration is not stored.
-
-% CFGMGR_OSERROR_WHILE_WRITING_CONFIGURATION Unable to write configuration file; configuration not stored: %1
-There was an OS error from the system while the configuration manager
-was trying to write the configuration database to disk. The specific
-error is given. The most likely cause is that the system does not have
-write access to the configuration database file. The updated
-configuration is not stored.
-
-% CFGMGR_STOPPED_BY_COMMAND received shutdown command, shutting down
-The configuration manager received a shutdown command, and is exiting.
-
-% CFGMGR_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down
-There was a keyboard interrupt signal to stop the cfgmgr daemon. The
-daemon will now shut down.
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-Classes to store configuration data and module specifications
-
-Used by the config manager, (python) modules, and UI's (those last
-two through the classes in ccsession)
-"""
-
-import isc.cc.data
-import isc.config.module_spec
-import ast
-import copy
-import sys
-
-class ConfigDataError(Exception): pass
-
-BIND10_CONFIG_DATA_VERSION = 3
-
-# Helper functions
-def spec_part_is_list(spec_part):
- """Returns True if the given spec_part is a dict that contains a
- list specification, and False otherwise."""
- return (type(spec_part) == dict and 'list_item_spec' in spec_part)
-
-def spec_part_is_map(spec_part):
- """Returns True if the given spec_part is a dict that contains a
- map specification, and False otherwise."""
- return (type(spec_part) == dict and 'map_item_spec' in spec_part)
-
-def spec_part_is_named_set(spec_part):
- """Returns True if the given spec_part is a dict that contains a
- named_set specification, and False otherwise."""
- return (type(spec_part) == dict and 'named_set_item_spec' in spec_part)
-
-def spec_part_is_any(spec_part):
- """Returns true if the given spec_part specifies an element of type
- any, and False otherwise.
- """
- return (type(spec_part) == dict and 'item_type' in spec_part and
- spec_part['item_type'] == "any")
-
-def _type_as_string(value):
- if type(value) == int:
- return 'integer'
- elif type(value) == float:
- return 'real'
- elif type(value) == bool:
- return 'boolean'
- elif type(value) == str:
- return 'string'
- elif type(value) == list:
- return 'list'
- elif type(value) == dict:
- return 'map'
- else:
- return '<unknown>'
-
-def check_type(spec_part, value):
- """Does nothing if the value is of the correct type given the
- specification part relevant for the value. Raises an
- isc.cc.data.DataTypeError exception if not. spec_part can be
- retrieved with find_spec_part()"""
- if type(spec_part) == dict and 'item_type' in spec_part:
- data_type = spec_part['item_type']
- else:
- raise isc.cc.data.DataTypeError(str("Incorrect specification part for type checking"))
-
- if data_type == "integer":
- if type(value) != int:
- raise isc.cc.data.DataTypeError('%s is not an integer (%s was passed)' % \
- (str(value), _type_as_string(value)))
- if value > sys.maxsize:
- raise isc.cc.data.DataTypeError('%s is too large an integer' % \
- (str(value)))
- elif data_type == "real":
- if type(value) != float:
- raise isc.cc.data.DataTypeError('%s is not a real (%s was passed)' % \
- (str(value), _type_as_string(value)))
- if float(value) > sys.float_info.max:
- raise isc.cc.data.DataTypeError('%s is too large for a float' % \
- (str(value)))
- elif data_type == "boolean" and type(value) != bool:
- raise isc.cc.data.DataTypeError('%s is not a boolean (%s was passed)' % \
- (str(value), _type_as_string(value)))
- elif data_type == "string" and type(value) != str:
- raise isc.cc.data.DataTypeError('%s is not a string (%s was passed)' % \
- (str(value), _type_as_string(value)))
- elif data_type == "list":
- if type(value) != list:
- raise isc.cc.data.DataTypeError('%s is not a list (%s was passed)' % \
- (str(value), _type_as_string(value)))
- else:
- for element in value:
- check_type(spec_part['list_item_spec'], element)
- elif data_type == "map" and type(value) != dict:
- # todo: check types of map contents too
- raise isc.cc.data.DataTypeError('%s is not a map (%s was passed)' % \
- (str(value), _type_as_string(value)))
-
-def convert_type(spec_part, value):
- """Convert the given value(type is string) according specification
- part relevant for the value. Raises an isc.cc.data.DataTypeError
- exception if conversion failed.
- """
- if type(spec_part) == dict and 'item_type' in spec_part:
- data_type = spec_part['item_type']
- else:
- raise isc.cc.data.DataTypeError(str("Incorrect specification part for type conversion"))
-
- try:
- if data_type == "integer":
- return int(value)
- elif data_type == "real":
- return float(value)
- elif data_type == "boolean":
- return str.lower(str(value)) != 'false'
- elif data_type == "string":
- return str(value)
- elif data_type == "list":
- ret = []
- if type(value) == list:
- for item in value:
- ret.append(convert_type(spec_part['list_item_spec'], item))
- elif type(value) == str:
- value = value.split(',')
- for item in value:
- sub_value = item.split()
- for sub_item in sub_value:
- ret.append(convert_type(spec_part['list_item_spec'],
- sub_item))
-
- if ret == []:
- raise isc.cc.data.DataTypeError(str(value) + " is not a list")
-
- return ret
- elif data_type == "map":
- try:
- map = ast.literal_eval(value)
- if type(map) == dict:
- # todo: check types of map contents too
- return map
- else:
- raise isc.cc.data.DataTypeError(
- "Value in convert_type not a string "
- "specifying a dict")
- except SyntaxError as se:
- raise isc.cc.data.DataTypeError("Error parsing map: " + str(se))
- else:
- return value
- except ValueError as err:
- raise isc.cc.data.DataTypeError(str(err))
- except TypeError as err:
- raise isc.cc.data.DataTypeError(str(err))
-
-def _get_map_or_list(spec_part):
- """Returns the list or map specification if this is a list or a
- map specification part. If not, returns the given spec_part
- itself"""
- if spec_part_is_map(spec_part):
- return spec_part["map_item_spec"]
- elif spec_part_is_list(spec_part):
- return spec_part["list_item_spec"]
- else:
- return spec_part
-
-def _find_spec_part_single(cur_spec, id_part):
- """Find the spec part for the given (partial) name. This partial
- name does not contain separators ('/'), and the specification
- part should be a direct child of the given specification part.
- id_part may contain list selectors, which will be ignored.
- Returns the child part.
- Raises DataNotFoundError if it was not found."""
- # strip list selector part
- # don't need it for the spec part, so just drop it
- id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
-
- # The specification we want a sub-part for should be either a
- # list or a map, which is internally represented by a dict with
- # an element 'map_item_spec', a dict with an element 'list_item_spec',
- # or a list (when it is the 'main' config_data element of a module).
- if spec_part_is_map(cur_spec):
- for cur_spec_item in cur_spec['map_item_spec']:
- if cur_spec_item['item_name'] == id:
- return cur_spec_item
- # not found
- raise isc.cc.data.DataNotFoundError(id + " not found")
- elif spec_part_is_list(cur_spec):
- if cur_spec['item_name'] == id:
- return cur_spec['list_item_spec']
- # not found
- raise isc.cc.data.DataNotFoundError(id + " not found")
- elif type(cur_spec) == dict and 'named_set_item_spec' in cur_spec.keys():
- return cur_spec['named_set_item_spec']
- elif type(cur_spec) == list:
- for cur_spec_item in cur_spec:
- if cur_spec_item['item_name'] == id:
- return cur_spec_item
- # not found
- raise isc.cc.data.DataNotFoundError(id + " not found")
- else:
- raise isc.cc.data.DataNotFoundError("Not a correct config specification")
-
-def find_spec_part(element, identifier, strict_identifier = True):
- """find the data definition for the given identifier
- returns either a map with 'item_name' etc, or a list of those
- Parameters:
- element: The specification element to start the search in
- identifier: The element to find (relative to element above)
- strict_identifier: If True (the default), additional checking occurs.
- Currently the only check is whether a list index is
- specified (except for the last part of the
- identifier)
- Raises a DataNotFoundError if the data is not found, or if
- strict_identifier is True and any non-final identifier parts
- (i.e. before the last /) identify a list element and do not contain
- an index.
- Returns the spec element identified by the given identifier.
- """
- if identifier == "":
- return element
- id_parts = identifier.split("/")
- id_parts[:] = (value for value in id_parts if value != "")
- cur_el = element
-
- # up to the last element, if the result is a map or a list,
- # we want its subspecification (i.e. list_item_spec or
- # map_item_spec). For the last element in the identifier we
- # always want the 'full' spec of the item
- for id_part in id_parts[:-1]:
- cur_el = _find_spec_part_single(cur_el, id_part)
- # As soon as we find 'any', return that
- if cur_el["item_type"] == "any":
- return cur_el
- if strict_identifier and spec_part_is_list(cur_el) and\
- not isc.cc.data.identifier_has_list_index(id_part):
- raise isc.cc.data.DataNotFoundError(id_part +
- " is a list and needs an index")
- cur_el = _get_map_or_list(cur_el)
-
- cur_el = _find_spec_part_single(cur_el, id_parts[-1])
- # Due to the raw datatypes we use, it is safer to return a deep copy here
- return copy.deepcopy(cur_el)
-
-def spec_name_list(spec, prefix="", recurse=False):
- """Returns a full list of all possible item identifiers in the
- specification (part). Raises a ConfigDataError if spec is not
- a correct spec (as returned by ModuleSpec.get_config_spec()"""
- result = []
- if prefix != "" and not prefix.endswith("/"):
- prefix += "/"
- if type(spec) == dict:
- if spec_part_is_map(spec):
- for map_el in spec['map_item_spec']:
- name = map_el['item_name']
- if map_el['item_type'] == 'map':
- name += "/"
- if recurse and spec_part_is_map(map_el):
- result.extend(spec_name_list(map_el['map_item_spec'], prefix + map_el['item_name'], recurse))
- else:
- result.append(prefix + name)
- elif 'named_set_item_spec' in spec:
- # we added a '/' above, but in this one case we don't want it
- result.append(prefix[:-1])
- # ignore any
- elif not spec_part_is_any(spec):
- for name in spec:
- result.append(prefix + name + "/")
- if recurse:
- result.extend(spec_name_list(spec[name], name, recurse))
- elif type(spec) == list:
- for list_el in spec:
- if 'item_name' in list_el:
- if list_el['item_type'] == "map" and recurse:
- result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
- else:
- name = list_el['item_name']
- result.append(prefix + name)
- else:
- raise ConfigDataError("Bad specification")
- else:
- raise ConfigDataError("Bad specification")
- return result
-
-class ConfigData:
- """This class stores the module specs and the current non-default
- config values. It provides functions to get the actual value or
- the default value if no non-default value has been set"""
-
- def __init__(self, specification):
- """Initialize a ConfigData instance. If specification is not
- of type ModuleSpec, a ConfigDataError is raised."""
- if type(specification) != isc.config.ModuleSpec:
- raise ConfigDataError("specification is of type " + str(type(specification)) + ", not ModuleSpec")
- self.specification = specification
- self.data = {}
-
- def get_value(self, identifier):
- """Returns a tuple where the first item is the value at the
- given identifier, and the second item is a bool which is
- true if the value is an unset default. Raises an
- isc.cc.data.DataNotFoundError if the identifier is bad"""
- value = isc.cc.data.find_no_exc(self.data, identifier)
- if value != None:
- return value, False
- spec = find_spec_part(self.specification.get_config_spec(), identifier)
- if spec and 'item_default' in spec:
- return spec['item_default'], True
- return None, False
-
- def get_default_value(self, identifier):
- """Returns the default from the specification, or None if there
- is no default"""
- # We are searching for the default value, so we can set
- # strict_identifier to false (in fact, we need to; we may not know
- # some list indices, or they may not exist, we are looking for
- # a default value for a reason here).
- spec = find_spec_part(self.specification.get_config_spec(),
- identifier, False)
- if spec and 'item_default' in spec:
- return spec['item_default']
- else:
- return None
-
- def get_module_spec(self):
- """Returns the ModuleSpec object associated with this ConfigData"""
- return self.specification
-
- def set_local_config(self, data):
- """Set the non-default config values, as passed by cfgmgr"""
- self.data = data
-
- def get_local_config(self):
- """Returns the non-default config values in a dict"""
- return self.data
-
- def get_item_list(self, identifier = None, recurse = False):
- """Returns a list of strings containing the full identifiers of
- all 'sub'options at the given identifier. If recurse is True,
- it will also add all identifiers of all children, if any"""
- if identifier:
- spec = find_spec_part(self.specification.get_config_spec(), identifier)
- return spec_name_list(spec, identifier + "/")
- return spec_name_list(self.specification.get_config_spec(), "", recurse)
-
- def get_full_config(self):
- """Returns a dict containing identifier: value elements, for
- all configuration options for this module. If there is
- a local setting, that will be used. Otherwise the value
- will be the default as specified by the module specification.
- If there is no default and no local setting, the value will
- be None"""
- items = self.get_item_list(None, True)
- result = {}
- for item in items:
- value, default = self.get_value(item)
- result[item] = value
- return result
-
-# should we just make a class for these?
-def _create_value_map_entry(name, type, value, status = None):
- entry = {}
- entry['name'] = name
- entry['type'] = type
- entry['value'] = value
- entry['modified'] = False
- entry['default'] = False
- if status == MultiConfigData.LOCAL:
- entry['modified'] = True
- if status == MultiConfigData.DEFAULT:
- entry['default'] = True
- return entry
-
-class MultiConfigData:
- """This class stores the module specs, current non-default
- configuration values and 'local' (uncommitted) changes for
- multiple modules"""
- LOCAL = 1
- CURRENT = 2
- DEFAULT = 3
- NONE = 4
-
- def __init__(self):
- self._specifications = {}
- self._current_config = {}
- self._local_changes = {}
-
- def clear_specifications(self):
- """Remove all known module specifications"""
- self._specifications = {}
-
- def set_specification(self, spec):
- """Add or update a ModuleSpec. Raises a ConfigDataError is spec is not a ModuleSpec"""
- if type(spec) != isc.config.ModuleSpec:
- raise ConfigDataError("not a datadef: " + str(type(spec)))
- self._specifications[spec.get_module_name()] = spec
-
- def remove_specification(self, module_name):
- """Removes the specification with the given module name. Does nothing if it wasn't there."""
- if module_name in self._specifications:
- del self._specifications[module_name]
-
- def have_specification(self, module_name):
- """Returns True if we have a specification for the module with the given name.
- Returns False if we do not."""
- return module_name in self._specifications
-
- def get_module_spec(self, module):
- """Returns the ModuleSpec for the module with the given name.
- If there is no such module, it returns None"""
- if module in self._specifications:
- return self._specifications[module]
- else:
- return None
-
- def find_spec_part(self, identifier):
- """Returns the specification for the item at the given
- identifier, or None if not found. The first part of the
- identifier (up to the first /) is interpreted as the module
- name. Returns None if not found, or if identifier is not a
- string.
- If an index is given for a List-type element, it returns
- the specification of the list elements, not of the list itself
- """
- if type(identifier) != str or identifier == "":
- return None
- if identifier[0] == '/':
- identifier = identifier[1:]
- module, sep, id = identifier.partition("/")
- if id != "":
- id, indices = isc.cc.data.split_identifier_list_indices(id)
- else:
- indices = None
- try:
- spec_part = find_spec_part(self._specifications[module].get_config_spec(), id)
- if indices is not None and spec_part_is_list(spec_part):
- return spec_part['list_item_spec']
- else:
- return spec_part
- except isc.cc.data.DataNotFoundError as dnfe:
- return None
- except KeyError as ke:
- return None
-
- # this function should only be called by __request_config
- def _set_current_config(self, config):
- """Replace the full current config values."""
- self._current_config = config
-
- def get_current_config(self):
- """Returns the current configuration as it is known by the
- configuration manager. It is a dict where the first level is
- the module name, and the value is the config values for
- that module"""
- return self._current_config
-
- def get_local_changes(self):
- """Returns the local config changes, i.e. those that have not
- been committed yet and are not known by the configuration
- manager or the modules."""
- return self._local_changes
-
- def set_local_changes(self, new_local_changes):
- """Sets the entire set of local changes, used when reverting
- changes done automatically in case there was a problem (e.g.
- when executing commands from a script that fails halfway
- through).
- """
- self._local_changes = new_local_changes
-
- def clear_local_changes(self):
- """Reverts all local changes"""
- self._local_changes = {}
-
- def get_local_value(self, identifier):
- """Returns a specific local (uncommitted) configuration value,
- as specified by the identifier. If the local changes do not
- contain a new setting for this identifier, or if the
- identifier cannot be found, None is returned. See
- get_value() for a general way to find a configuration value
- """
- return isc.cc.data.find_no_exc(self._local_changes, identifier)
-
- def get_current_value(self, identifier):
- """Returns the current non-default value as known by the
- configuration manager, or None if it is not set.
- See get_value() for a general way to find a configuration
- value
- """
- return isc.cc.data.find_no_exc(self._current_config, identifier)
-
- def get_default_value(self, identifier):
- """Returns the default value for the given identifier as
- specified by the module specification, or None if there is
- no default or the identifier could not be found.
- See get_value() for a general way to find a configuration
- value
- """
- try:
- if identifier[0] == '/':
- identifier = identifier[1:]
- module, sep, id = identifier.partition("/")
- # if there is a 'higher-level' list index specified, we need
- # to check if that list specification has a default that
- # overrides the more specific default in the final spec item
- # (ie. list_default = [1, 2, 3], list_item_spec=int, default=0)
- # def default list[1] should return 2, not 0
- id_parts = isc.cc.data.split_identifier(id)
- id_prefix = ""
- while len(id_parts) > 0:
- id_part = id_parts.pop(0)
- item_id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
- id_list = module + "/" + id_prefix + "/" + item_id
- id_prefix += "/" + id_part
- part_spec = find_spec_part(self._specifications[module].get_config_spec(), id_prefix)
- if part_spec['item_type'] == 'named_set':
- # For named sets, the identifier is partly defined
- # by which values are actually present, and not
- # purely by the specification.
- # So if there is a part of the identifier left,
- # we need to look up the value, then see if that
- # contains the next part of the identifier we got
- if len(id_parts) == 0:
- if 'item_default' in part_spec:
- return part_spec['item_default']
- else:
- return None
- id_part = id_parts.pop(0)
- item_id, list_indices =\
- isc.cc.data.split_identifier_list_indices(id_part)
-
- named_set_value, type = self.get_value(id_list)
- if item_id in named_set_value.keys():
- result = named_set_value[item_id]
- # If the item is a list and we have indices in the
- # identifier part, continue with the item pointed to
- # by those indices
- if list_indices is not None:
- while len(list_indices) > 0:
- result = result[list_indices.pop(0)]
-
- if len(id_parts) > 0:
- # we are looking for the *default* value.
- # so if not present in here, we need to
- # lookup the one from the spec
- rest_of_id = "/".join(id_parts)
- result = isc.cc.data.find_no_exc(result, rest_of_id)
- if result is None:
- spec_part = self.find_spec_part(identifier)
- if 'item_default' in spec_part:
- return spec_part['item_default']
- return result
- else:
- return result
- else:
- return None
- elif list_indices is not None:
- # there's actually two kinds of default here for
- # lists; they can have a default value (like an
- # empty list), but their elements can also have
- # default values.
- # So if the list item *itself* is a default,
- # we need to get the value out of that. If not, we
- # need to find the default for the specific element.
- list_value, type = self.get_value(id_list)
- list_spec = find_spec_part(self._specifications[module].get_config_spec(), id_prefix)
- if type == self.DEFAULT:
- if 'item_default' in list_spec:
- list_value = list_spec['item_default']
- for i in list_indices:
- if i < len(list_value):
- list_value = list_value[i]
- else:
- # out of range, return None
- return None
-
- if len(id_parts) > 0:
- rest_of_id = "/".join(id_parts)
- return isc.cc.data.find(list_value, rest_of_id)
- else:
- return list_value
- else:
- # we do have a non-default list, see if our indices
- # exist
- for i in list_indices:
- if i < len(list_value):
- list_value = list_value[i]
- else:
- # out of range, return None
- return None
-
- spec = find_spec_part(self._specifications[module].get_config_spec(), id)
- if 'item_default' in spec:
- # one special case, named_set
- if spec['item_type'] == 'named_set':
- return spec['item_default']
- else:
- return spec['item_default']
- else:
- return None
-
- except isc.cc.data.DataNotFoundError as dnfe:
- return None
-
- def get_value(self, identifier, default = True):
- """Returns a tuple containing value,status.
- The value contains the configuration value for the given
- identifier. The status reports where this value came from;
- it is one of: LOCAL, CURRENT, DEFAULT or NONE, corresponding
- (local change, current setting, default as specified by the
- specification, or not found at all). Does not check and
- set DEFAULT if the argument 'default' is False (default
- defaults to True)"""
- value = self.get_local_value(identifier)
- if value != None:
- return value, self.LOCAL
- value = self.get_current_value(identifier)
- if value != None:
- return value, self.CURRENT
- if default:
- value = self.get_default_value(identifier)
- if value is not None:
- return value, self.DEFAULT
- else:
- # get_default_value returns None for both
- # the cases where there is no default, and where
- # it is set to null, so we need to catch the latter
- spec_part = self.find_spec_part(identifier)
- if spec_part and 'item_default' in spec_part and\
- spec_part['item_default'] is None:
- return None, self.DEFAULT
- return None, self.NONE
-
- def _append_value_item(self, result, spec_part, identifier, all, first = False):
- # Look at the spec; it is a list of items, or a map containing 'item_name' etc
- if type(spec_part) == list:
- for spec_part_element in spec_part:
- spec_part_element_name = spec_part_element['item_name']
- self._append_value_item(result, spec_part_element, identifier + "/" + spec_part_element_name, all)
- elif type(spec_part) == dict:
- # depending on item type, and the value of argument 'all'
- # we need to either add an item, or recursively go on
- # In the case of a list that is empty, we do need to show that
- item_name = spec_part['item_name']
- item_type = spec_part['item_type']
- if item_type == "list" and (all or first):
- spec_part_list = spec_part['list_item_spec']
- list_value, status = self.get_value(identifier)
- # If not set, and no default, lists will show up as 'None',
- # but it's better to treat it as an empty list then
- if list_value is None:
- list_value = []
-
- if type(list_value) != list:
- # the identifier specified a single element
- self._append_value_item(result, spec_part_list, identifier, all)
- else:
- list_len = len(list_value)
- if len(list_value) == 0 and (all or first):
- entry = _create_value_map_entry(identifier,
- item_type,
- [], status)
- result.append(entry)
- else:
- for i in range(len(list_value)):
- self._append_value_item(result, spec_part_list, "%s[%d]" % (identifier, i), all)
- elif item_type == "map":
- value, status = self.get_value(identifier)
- # just show the specific contents of a map, we are
- # almost never interested in just its name
- spec_part_map = spec_part['map_item_spec']
- self._append_value_item(result, spec_part_map, identifier, all)
- elif item_type == "named_set":
- value, status = self.get_value(identifier)
-
- # show just the one entry, when either the map is empty,
- # or when this is element is not requested specifically
- if len(value.keys()) == 0:
- entry = _create_value_map_entry(identifier,
- item_type,
- {}, status)
- result.append(entry)
- elif not first and not all:
- entry = _create_value_map_entry(identifier,
- item_type,
- None, status)
- result.append(entry)
- else:
- spec_part_named_set = spec_part['named_set_item_spec']
- for entry in value:
- self._append_value_item(result,
- spec_part_named_set,
- identifier + "/" + entry,
- all)
- else:
- value, status = self.get_value(identifier)
- if status == self.NONE and not spec_part['item_optional']:
- raise isc.cc.data.DataNotFoundError(identifier + " not found")
-
- entry = _create_value_map_entry(identifier,
- item_type,
- value, status)
- result.append(entry)
- return
-
-
- def get_value_maps(self, identifier = None, all = False):
- """Returns a list of dicts, containing the following values:
- name: name of the entry (string)
- type: string containing the type of the value (or 'module')
- value: value of the entry if it is a string, int, double or bool
- modified: true if the value is a local change that has not
- been committed
- default: true if the value has not been changed (i.e. the
- value is the default from the specification)
- TODO: use the consts for those last ones
- Throws DataNotFoundError if the identifier is bad
- """
- result = []
- if not identifier or identifier == "/":
- # No identifier, so we need the list of current modules
- for module in self._specifications.keys():
- if all:
- spec = self.get_module_spec(module)
- if spec:
- spec_part = spec.get_config_spec()
- self._append_value_item(result, spec_part, module, all, True)
- else:
- entry = _create_value_map_entry(module, 'module', None)
- result.append(entry)
- else:
- # Strip off start and end slashes, if they are there
- if len(identifier) > 0 and identifier[0] == '/':
- identifier = identifier[1:]
- if len(identifier) > 0 and identifier[-1] == '/':
- identifier = identifier[:-1]
- module, sep, id = identifier.partition('/')
- spec = self.get_module_spec(module)
- if spec:
- spec_part = find_spec_part(spec.get_config_spec(), id)
- self._append_value_item(result, spec_part, identifier, all, True)
- return result
-
- def unset(self, identifier):
- """
- Reset the value to default.
- """
- spec_part = self.find_spec_part(identifier)
- if spec_part is not None:
- isc.cc.data.unset(self._local_changes, identifier)
- else:
- raise isc.cc.data.DataNotFoundError(identifier + "not found")
-
- def set_value(self, identifier, value):
- """Set the local value at the given identifier to value. If
- there is a specification for the given identifier, the type
- is checked."""
- spec_part = self.find_spec_part(identifier)
- if spec_part is not None:
- if value is not None:
- id, list_indices = isc.cc.data.split_identifier_list_indices(identifier)
- if list_indices is not None \
- and spec_part['item_type'] == 'list':
- spec_part = spec_part['list_item_spec']
- check_type(spec_part, value)
- else:
- raise isc.cc.data.DataNotFoundError(identifier + " not found")
-
- # Since we do not support list diffs (yet?), we need to
- # copy the currently set list of items to _local_changes
- # if we want to modify an element in there
- # (for any list indices specified in the full identifier)
- id_parts = isc.cc.data.split_identifier(identifier)
- cur_id_part = '/'
- for id_part in id_parts:
- id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
- cur_value, status = self.get_value(cur_id_part + id)
- # Check if the value was there in the first place
- # If we are at the final element, we do not care whether we found
- # it, since if we have reached this point and it did not exist,
- # it was apparently an optional value without a default.
- if status == MultiConfigData.NONE and cur_id_part != "/" and\
- cur_id_part + id != identifier:
- raise isc.cc.data.DataNotFoundError(id_part +
- " not found in " +
- cur_id_part)
- if list_indices is not None:
- # And check if we don't set something outside of any
- # list
- cur_list = cur_value
- for list_index in list_indices:
- if type(cur_list) != list:
- raise isc.cc.data.DataTypeError(id + " is not a list")
- if list_index >= len(cur_list):
- raise isc.cc.data.DataNotFoundError("No item " +
- str(list_index) + " in " + id_part)
- else:
- cur_list = cur_list[list_index]
- if status != MultiConfigData.LOCAL:
- isc.cc.data.set(self._local_changes,
- cur_id_part + id,
- cur_value)
- cur_id_part = cur_id_part + id_part + "/"
-
- # We also need to copy to local if we are changing a named set,
- # so that the other items in the set do not disappear
- if spec_part_is_named_set(self.find_spec_part(cur_id_part)):
- ns_value, ns_status = self.get_value(cur_id_part)
- if ns_status != MultiConfigData.LOCAL:
- isc.cc.data.set(self._local_changes,
- cur_id_part,
- ns_value)
- isc.cc.data.set(self._local_changes, identifier, value)
-
- def _get_list_items(self, item_name):
- """This method is used in get_config_item_list, to add list
- indices and named_set names to the completion list. If
- the given item_name is for a list or named_set, it'll
- return a list of those (appended to item_name), otherwise
- the list will only contain the item_name itself.
-
- If the item is a named set, and it's contents are maps
- or named_sets as well, a / is appended to the result
- strings.
-
- If the item is a list, this method is then called recursively
- for each list entry.
-
- This behaviour is slightly arbitrary, and currently reflects
- the most probable way the resulting data should look like;
- for lists, bindctl would always expect their contents to
- be added as well. For named_sets, however, we do not
- do recursion, since the resulting list may be too long.
- This will probably change in a revision of the way this
- data is handled; ideally, the result should always recurse,
- but only up to a limited depth, and the resulting list
- should probably be paginated clientside.
-
- Parameters:
- item_name (string): the (full) identifier for the list or
- named_set to enumerate.
-
- Returns a list of strings with item names
-
- Examples:
- _get_list_items("Module/some_item")
- where item is not a list of a named_set, or where
- said list or named set is empty, returns
- ["Module/some_item"]
- _get_list_items("Module/named_set")
- where the named_set contains items with names 'a'
- and 'b', returns
- [ "Module/named_set/a", "Module/named_set/b" ]
- _get_list_items("Module/named_set_of_maps")
- where the named_set contains items with names 'a'
- and 'b', and those items are maps themselves
- (or other named_sets), returns
- [ "Module/named_set/a/", "Module/named_set/b/" ]
- _get_list_items("Module/list")
- where the list contains 2 elements, returns
- [ "Module/list[0]", "Module/list[1]" ]
- """
- spec_part = self.find_spec_part(item_name)
- if spec_part_is_named_set(spec_part):
- values, _ = self.get_value(item_name)
- if values is not None and len(values) > 0:
- subslash = ""
- if spec_part['named_set_item_spec']['item_type'] == 'map' or\
- spec_part['named_set_item_spec']['item_type'] == 'named_set':
- subslash = "/"
- # Don't recurse for named_sets (so as not to return too
- # much data), but do add a / so the client so that
- # the user can immediately tab-complete further if needed.
- return [ item_name + "/" + v + subslash for v in values.keys() ]
- else:
- return [ item_name ]
- elif spec_part_is_list(spec_part):
- values, _ = self.get_value(item_name)
- if values is not None and len(values) > 0:
- result = []
- for i in range(len(values)):
- name = item_name + '[%d]' % i
- # Recurse for list entries, so that its sub-contents
- # are also added to the result
- result.extend(self._get_list_items(name))
- return result
- else:
- return [ item_name ]
- else:
- return [ item_name ]
-
- def get_config_item_list(self, identifier = None, recurse = False):
- """Returns a list of strings containing the item_names of
- the child items at the given identifier. If no identifier is
- specified, returns a list of module names. The first part of
- the identifier (up to the first /) is interpreted as the
- module name"""
- if identifier and identifier != "/":
- if identifier.startswith("/"):
- identifier = identifier[1:]
- spec = self.find_spec_part(identifier)
- spec_list = spec_name_list(spec, identifier + "/", recurse)
- result_list = []
- for spec_name in spec_list:
- result_list.extend(self._get_list_items(spec_name))
- return result_list
- else:
- if recurse:
- id_list = []
- for module in self._specifications.keys():
- id_list.extend(spec_name_list(self.find_spec_part(module), module, recurse))
- return id_list
- else:
- return list(self._specifications.keys())
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# No namespace declaration - these constants go in the global namespace
-# of the config_messages python module.
-
-# since these messages are for the python config library, care must
-# be taken that names do not conflict with the messages from the c++
-# config library. A checker script should verify that, but we do not
-# have that at this moment. So when adding a message, make sure that
-# the name is not already used in src/lib/config/config_messages.mes
-
-% CONFIG_GET_FAILED error getting configuration from cfgmgr: %1
-The configuration manager returned an error response when the module
-requested its configuration. The full error message answer from the
-configuration manager is appended to the log error.
-
-% CONFIG_LOG_CONFIG_ERRORS error(s) in logging configuration: %1
-There was a logging configuration update, but the internal validator
-for logging configuration found that it contained errors. The errors
-are shown, and the update is ignored.
-
-% CONFIG_SESSION_STOPPING_FAILED error sending stopping message: %1
-There was a problem when sending a message signaling that the module using
-this CCSession is stopping. This message is sent so that the rest of the
-system is aware that the module is no longer running. Apart from logging
-this message, the error itself is ignored, and the ModuleCCSession is
-still stopped. The specific exception message is printed.
+++ /dev/null
-# Copyright (C) 2009 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""Module Specifications
-
- A module specification holds the information about what configuration
- a module can have, and what commands it understands. It provides
- functions to read it from a .spec file, and to validate a given
- set of data against the specification
-"""
-
-import json
-import sys
-import time
-
-import isc.cc.data
-
-# file objects are passed around as _io.TextIOWrapper objects
-# import that so we can check those types
-
-class ModuleSpecError(Exception):
- """This exception is raised it the ModuleSpec fails to initialize
- or if there is a failure or parse error reading the specification
- file"""
- pass
-
-def module_spec_from_file(spec_file, check = True):
- """Returns a ModuleSpec object defined by the file at spec_file.
- If check is True, the contents are verified. If there is an error
- in those contents, a ModuleSpecError is raised.
- A ModuleSpecError is also raised if the file cannot be read, or
- if it is not valid JSON."""
- module_spec = None
- try:
- if hasattr(spec_file, 'read'):
- json_str = spec_file.read()
- module_spec = json.loads(json_str)
- elif type(spec_file) == str:
- file = open(spec_file)
- json_str = file.read()
- module_spec = json.loads(json_str)
- file.close()
- else:
- raise ModuleSpecError("spec_file not a str or file-like object")
- except ValueError as ve:
- raise ModuleSpecError("JSON parse error: " + str(ve))
- except IOError as ioe:
- raise ModuleSpecError("JSON read error: " + str(ioe))
-
- if 'module_spec' not in module_spec:
- raise ModuleSpecError("Data definition has no module_spec element")
-
- result = ModuleSpec(module_spec['module_spec'], check)
- return result
-
-class ModuleSpec:
- def __init__(self, module_spec, check = True):
- """Initializes a ModuleSpec object from the specification in
- the given module_spec (which must be a dict). If check is
- True, the contents are verified. Raises a ModuleSpec error
- if there is something wrong with the contents of the dict"""
- if type(module_spec) != dict:
- raise ModuleSpecError("module_spec is of type " + str(type(module_spec)) + ", not dict")
- if check:
- _check(module_spec)
- self._module_spec = module_spec
-
- def validate_config(self, full, data, errors = None):
- """Check whether the given piece of data conforms to this
- data definition. If so, it returns True. If not, it will
- return false. If errors is given, and is an array, a string
- describing the error will be appended to it. The current
- version stops as soon as there is one error so this list
- will not be exhaustive. If 'full' is true, it also errors on
- non-optional missing values. Set this to False if you want to
- validate only a part of a configuration tree (like a list of
- non-default values)"""
- data_def = self.get_config_spec()
- if data_def is not None:
- return _validate_spec_list(data_def, full, data, errors)
- else:
- # no spec, always bad
- if errors is not None:
- errors.append("No config_data specification")
- return False
-
- def validate_command(self, cmd_name, cmd_params, errors = None):
- '''Check whether the given piece of command conforms to this
- command definition. If so, it reutrns True. If not, it will
- return False. If errors is given, and is an array, a string
- describing the error will be appended to it. The current version
- stops as soon as there is one error.
- cmd_name is command name to be validated, cmd_params includes
- command's parameters needs to be validated. cmd_params must
- be a map, with the format like:
- {param1_name: param1_value, param2_name: param2_value}
- '''
- cmd_spec = self.get_commands_spec()
- if not cmd_spec:
- return False
-
- for cmd in cmd_spec:
- if cmd['command_name'] != cmd_name:
- continue
- return _validate_spec_list(cmd['command_args'], True, cmd_params, errors)
-
- return False
-
- def validate_statistics(self, full, stat, errors = None):
- """Check whether the given piece of data conforms to this
- data definition. If so, it returns True. If not, it will
- return false. If errors is given, and is an array, a string
- describing the error will be appended to it. The current
- version stops as soon as there is one error so this list
- will not be exhaustive. If 'full' is true, it also errors on
- non-optional missing values. Set this to False if you want to
- validate only a part of a statistics tree (like a list of
- non-default values). Also it checks 'item_format' in case
- of time"""
- stat_spec = self.get_statistics_spec()
- if stat_spec is not None:
- return _validate_spec_list(stat_spec, full, stat, errors)
- else:
- # no spec, always bad
- if errors is not None:
- errors.append("No statistics specification")
- return False
-
- def get_module_name(self):
- """Returns a string containing the name of the module as
- specified by the specification given at __init__()"""
- return self._module_spec['module_name']
-
- def get_module_description(self):
- """Returns a string containing the description of the module as
- specified by the specification given at __init__().
- Returns an empty string if there is no description.
- """
- if 'module_description' in self._module_spec:
- return self._module_spec['module_description']
- else:
- return ""
-
- def get_full_spec(self):
- """Returns a dict representation of the full module specification"""
- return self._module_spec
-
- def get_config_spec(self):
- """Returns a dict representation of the configuration data part
- of the specification, or None if there is none."""
- if 'config_data' in self._module_spec:
- return self._module_spec['config_data']
- else:
- return None
-
- def get_commands_spec(self):
- """Returns a dict representation of the commands part of the
- specification, or None if there is none."""
- if 'commands' in self._module_spec:
- return self._module_spec['commands']
- else:
- return None
-
- def get_statistics_spec(self):
- """Returns a dict representation of the statistics part of the
- specification, or None if there is none."""
- if 'statistics' in self._module_spec:
- return self._module_spec['statistics']
- else:
- return None
-
- def __str__(self):
- """Returns a string representation of the full specification"""
- return self._module_spec.__str__()
-
-def _check(module_spec):
- """Checks the full specification. This is a dict that contains the
- element "module_spec", which is in itself a dict that
- must contain at least a "module_name" (string) and optionally
- a "config_data", a "commands" and a "statistics" element, all
- of which are lists of dicts. Raises a ModuleSpecError if there
- is a problem."""
- if type(module_spec) != dict:
- raise ModuleSpecError("data specification not a dict")
- if "module_name" not in module_spec:
- raise ModuleSpecError("no module_name in module_spec")
- if "module_description" in module_spec and \
- type(module_spec["module_description"]) != str:
- raise ModuleSpecError("module_description is not a string")
- if "config_data" in module_spec:
- _check_config_spec(module_spec["config_data"])
- if "commands" in module_spec:
- _check_command_spec(module_spec["commands"])
- if "statistics" in module_spec:
- _check_statistics_spec(module_spec["statistics"])
-
-def _check_config_spec(config_data):
- # config data is a list of items represented by dicts that contain
- # things like "item_name", depending on the type they can have
- # specific subitems
- """Checks a list that contains the configuration part of the
- specification. Raises a ModuleSpecError if there is a
- problem."""
- if type(config_data) != list:
- raise ModuleSpecError("config_data is of type " + str(type(config_data)) + ", not a list of items")
- for config_item in config_data:
- _check_item_spec(config_item)
-
-def _check_command_spec(commands):
- """Checks the list that contains a set of commands. Raises a
- ModuleSpecError is there is an error"""
- if type(commands) != list:
- raise ModuleSpecError("commands is not a list of commands")
- for command in commands:
- if type(command) != dict:
- raise ModuleSpecError("command in commands list is not a dict")
- if "command_name" not in command:
- raise ModuleSpecError("no command_name in command item")
- command_name = command["command_name"]
- if type(command_name) != str:
- raise ModuleSpecError("command_name not a string: " + str(type(command_name)))
- if "command_description" in command:
- if type(command["command_description"]) != str:
- raise ModuleSpecError("command_description not a string in " + command_name)
- if "command_args" in command:
- if type(command["command_args"]) != list:
- raise ModuleSpecError("command_args is not a list in " + command_name)
- for command_arg in command["command_args"]:
- if type(command_arg) != dict:
- raise ModuleSpecError("command argument not a dict in " + command_name)
- _check_item_spec(command_arg)
- else:
- raise ModuleSpecError("command_args missing in " + command_name)
- pass
-
-def _check_item_spec(config_item):
- """Checks the dict that defines one config item
- (i.e. containing "item_name", "item_type", etc.
- Raises a ModuleSpecError if there is an error"""
- if type(config_item) != dict:
- raise ModuleSpecError("item spec not a dict")
- if "item_name" not in config_item:
- raise ModuleSpecError("no item_name in config item")
- if type(config_item["item_name"]) != str:
- raise ModuleSpecError("item_name is not a string: " + str(config_item["item_name"]))
- item_name = config_item["item_name"]
- if "item_type" not in config_item:
- raise ModuleSpecError("no item_type in config item")
- item_type = config_item["item_type"]
- if type(item_type) != str:
- raise ModuleSpecError("item_type in " + item_name + " is not a string: " + str(type(item_type)))
- if item_type not in ["integer", "real", "boolean", "string", "list", "map", "named_set", "any"]:
- raise ModuleSpecError("unknown item_type in " + item_name + ": " + item_type)
- if "item_optional" in config_item:
- if type(config_item["item_optional"]) != bool:
- raise ModuleSpecError("item_default in " + item_name + " is not a boolean")
- if not config_item["item_optional"] and "item_default" not in config_item:
- raise ModuleSpecError("no default value for non-optional item " + item_name)
- else:
- raise ModuleSpecError("item_optional not in item " + item_name)
- if "item_default" in config_item:
- item_default = config_item["item_default"]
- if (item_type == "integer" and type(item_default) != int) or \
- (item_type == "real" and type(item_default) != float) or \
- (item_type == "boolean" and type(item_default) != bool) or \
- (item_type == "string" and type(item_default) != str) or \
- (item_type == "list" and type(item_default) != list) or \
- (item_type == "map" and type(item_default) != dict):
- raise ModuleSpecError("Wrong type for item_default in " + item_name)
- # TODO: once we have check_type, run the item default through that with the list|map_item_spec
- if item_type == "list":
- if "list_item_spec" not in config_item:
- raise ModuleSpecError("no list_item_spec in list item " + item_name)
- if type(config_item["list_item_spec"]) != dict:
- raise ModuleSpecError("list_item_spec in " + item_name + " is not a dict")
- _check_item_spec(config_item["list_item_spec"])
- if item_type == "map":
- if "map_item_spec" not in config_item:
- raise ModuleSpecError("no map_item_sepc in map item " + item_name)
- if type(config_item["map_item_spec"]) != list:
- raise ModuleSpecError("map_item_spec in " + item_name + " is not a list")
- for map_item in config_item["map_item_spec"]:
- if type(map_item) != dict:
- raise ModuleSpecError("map_item_spec element is not a dict")
- _check_item_spec(map_item)
- if 'item_format' in config_item and 'item_default' in config_item:
- item_format = config_item["item_format"]
- item_default = config_item["item_default"]
- if not _check_format(item_default, item_format):
- raise ModuleSpecError(
- "Wrong format for " + str(item_default) + " in " + str(item_name))
-
-def _check_statistics_spec(statistics):
- # statistics is a list of items represented by dicts that contain
- # things like "item_name", depending on the type they can have
- # specific subitems
- """Checks a list that contains the statistics part of the
- specification. Raises a ModuleSpecError if there is a
- problem."""
- if type(statistics) != list:
- raise ModuleSpecError("statistics is of type " + str(type(statistics))
- + ", not a list of items")
- for stat_item in statistics:
- _check_item_spec(stat_item)
- # Additionally checks if there are 'item_title' and
- # 'item_description'
- for item in [ 'item_title', 'item_description' ]:
- if item not in stat_item:
- raise ModuleSpecError("no " + item + " in statistics item")
-
-def _check_format(value, format_name):
- """Check if specified value and format are correct. Return True if
- is is correct."""
- # TODO: should be added other format types if necessary
- time_formats = { 'date-time' : "%Y-%m-%dT%H:%M:%SZ",
- 'date' : "%Y-%m-%d",
- 'time' : "%H:%M:%S" }
- for fmt in time_formats:
- if format_name == fmt:
- try:
- # reverse check
- return value == time.strftime(
- time_formats[fmt],
- time.strptime(value, time_formats[fmt]))
- except (ValueError, TypeError):
- break
- return False
-
-def _validate_type(spec, value, errors):
- """Returns true if the value is of the correct type given the
- specification"""
- data_type = spec['item_type']
- if data_type == "integer" and type(value) != int:
- if errors is not None:
- errors.append(str(value) + " should be an integer")
- return False
- elif data_type == "real" and type(value) != float:
- if errors is not None:
- errors.append(str(value) + " should be a real")
- return False
- elif data_type == "boolean" and type(value) != bool:
- if errors is not None:
- errors.append(str(value) + " should be a boolean")
- return False
- elif data_type == "string" and type(value) != str:
- if errors is not None:
- errors.append(str(value) + " should be a string")
- return False
- elif data_type == "list" and type(value) != list:
- if errors is not None:
- errors.append(str(value) + " should be a list")
- return False
- elif data_type == "map" and type(value) != dict:
- if errors is not None:
- errors.append(str(value) + " should be a map")
- return False
- elif data_type == "named_set" and type(value) != dict:
- if errors != None:
- errors.append(str(value) + " should be a map")
- return False
- else:
- return True
-
-def _validate_format(spec, value, errors):
- """Returns true if the value is of the correct format given the
- specification. And also return true if no 'item_format'"""
- if "item_format" in spec:
- item_format = spec['item_format']
- if not _check_format(value, item_format):
- if errors is not None:
- errors.append("format type of " + str(value)
- + " should be " + item_format)
- return False
- return True
-
-def _validate_item(spec, full, data, errors):
- if spec.get('item_type') == 'any':
- return True
- if not _validate_type(spec, data, errors):
- return False
- elif type(data) == list:
- list_spec = spec['list_item_spec']
- for data_el in data:
- if not _validate_type(list_spec, data_el, errors):
- return False
- if not _validate_format(list_spec, data_el, errors):
- return False
- if list_spec['item_type'] == "map":
- if not _validate_item(list_spec, full, data_el, errors):
- return False
- elif type(data) == dict:
- if 'map_item_spec' in spec:
- if not _validate_spec_list(spec['map_item_spec'], full, data, errors):
- return False
- else:
- named_set_spec = spec['named_set_item_spec']
- for data_el in data.values():
- if not _validate_type(named_set_spec, data_el, errors):
- return False
- if not _validate_item(named_set_spec, full, data_el, errors):
- return False
- elif not _validate_format(spec, data, errors):
- return False
- return True
-
-def _validate_spec(spec, full, data, errors):
- item_name = spec['item_name']
- item_optional = spec['item_optional']
-
- if not data and item_optional:
- return True
- elif item_name in data:
- return _validate_item(spec, full, data[item_name], errors)
- elif full and not item_optional:
- if errors is not None:
- errors.append("non-optional item " + item_name + " missing")
- return False
- else:
- return True
-
-def _validate_spec_list(module_spec, full, data, errors):
- # we do not return immediately, there may be more errors
- # so we keep a boolean to keep track if we found errors
- validated = True
-
- # check if the known items are correct
- for spec_item in module_spec:
- if not _validate_spec(spec_item, full, data, errors):
- validated = False
-
- # check if there are items in our data that are not in the
- # specification
- if data is not None:
- for item_name in data:
- found = False
- for spec_item in module_spec:
- if spec_item["item_name"] == item_name:
- found = True
- if not found and item_name != "version":
- if errors is not None:
- errors.append("unknown item " + item_name)
- validated = False
- return validated
+++ /dev/null
-/config_test
+++ /dev/null
-PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
-PYTESTS = config_data_test.py
-PYTESTS += module_spec_test.py
-EXTRA_DIST = $(PYTESTS)
-EXTRA_DIST += unittest_fakesession.py
-
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
-LIBRARY_PATH_PLACEHOLDER =
-if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
-endif
-
-# test using command-line arguments, so use check-local target instead of TESTS
-check-local:
-if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
- rm -f .coverage
- ${LN_S} $(abs_top_srcdir)/.coverage .coverage
-endif
- for pytest in $(PYTESTS) ; do \
- echo Running test: $$pytest ; \
- $(LIBRARY_PATH_PLACEHOLDER) \
- PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/config \
- B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
- B10_TEST_PLUGIN_DIR=$(abs_top_srcdir)/src/bin/cfgmgr/plugins \
- CONFIG_TESTDATA_PATH=$(abs_top_srcdir)/src/lib/config/tests/testdata \
- CONFIG_WR_TESTDATA_PATH=$(abs_top_builddir)/src/lib/config/tests/testdata \
- $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
- done
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Tests for the ConfigData and MultiConfigData classes
-#
-
-import unittest
-import os
-from isc.config.config_data import *
-from isc.config.module_spec import *
-
-class TestConfigData(unittest.TestCase):
- def setUp(self):
- if 'CONFIG_TESTDATA_PATH' in os.environ:
- self.data_path = os.environ['CONFIG_TESTDATA_PATH']
- else:
- self.data_path = "../../../testdata"
- spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
- self.cd = ConfigData(spec)
-
- #def test_module_spec_from_file(self):
- # spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
- # cd = ConfigData(spec)
- # self.assertEqual(cd.specification, spec)
- # self.assertEqual(cd.data, {})
- # self.assertRaises(ConfigDataError, ConfigData, 1)
-
- def test_check_type(self):
- config_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec22.spec").get_config_spec()
- spec_part = find_spec_part(config_spec, "value1")
- check_type(spec_part, 1)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 10000000000000000000000)
-
- spec_part = find_spec_part(config_spec, "value2")
- check_type(spec_part, 1.1)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 2.0000000e+308)
-
- spec_part = find_spec_part(config_spec, "value3")
- check_type(spec_part, True)
- check_type(spec_part, False)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
-
- spec_part = find_spec_part(config_spec, "value4")
- check_type(spec_part, "asdf")
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
-
- spec_part = find_spec_part(config_spec, "value5")
- check_type(spec_part, [1, 2])
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ "a", "b" ])
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
-
- spec_part = find_spec_part(config_spec, "value6")
- check_type(spec_part, { "value1": "aaa", "value2": 2 })
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
- self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
- #self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "value1": 1 })
-
- self.assertRaises(isc.cc.data.DataTypeError, check_type, config_spec, 1)
-
- def test_convert_type(self):
- config_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec22.spec").get_config_spec()
- spec_part = find_spec_part(config_spec, "value1")
- self.assertEqual(1, convert_type(spec_part, '1'))
- self.assertEqual(2, convert_type(spec_part, 2.1))
- self.assertEqual(2, convert_type(spec_part, '2'))
- self.assertEqual(3, convert_type(spec_part, '3'))
- self.assertEqual(1, convert_type(spec_part, True))
-
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "a")
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ 1, 2 ])
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, 1, "a")
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, { 'somedict': 'somevalue' }, "a")
-
- spec_part = find_spec_part(config_spec, "value2")
- self.assertEqual(1.1, convert_type(spec_part, '1.1'))
- self.assertEqual(123.0, convert_type(spec_part, '123'))
- self.assertEqual(1.0, convert_type(spec_part, True))
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "a")
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ 1, 2 ])
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
-
- spec_part = find_spec_part(config_spec, "value3")
- self.assertEqual(True, convert_type(spec_part, 'True'))
- self.assertEqual(False, convert_type(spec_part, 'False'))
- self.assertEqual(True, convert_type(spec_part, 1))
- self.assertEqual(True, convert_type(spec_part, 1.1))
- self.assertEqual(True, convert_type(spec_part, 'a'))
- self.assertEqual(True, convert_type(spec_part, [1, 2]))
- self.assertEqual(True, convert_type(spec_part, {'a' : 1}))
-
- spec_part = find_spec_part(config_spec, "value4")
- self.assertEqual('asdf', convert_type(spec_part, "asdf"))
- self.assertEqual('1', convert_type(spec_part, 1))
- self.assertEqual('1.1', convert_type(spec_part, 1.1))
- self.assertEqual('True', convert_type(spec_part, True))
-
- spec_part = find_spec_part(config_spec, "value5")
- self.assertEqual([1, 2], convert_type(spec_part, '1, 2'))
- self.assertEqual([1, 2, 3], convert_type(spec_part, '1 2 3'))
- self.assertEqual([1, 2, 3,4], convert_type(spec_part, '1 2 3, 4'))
- self.assertEqual([1], convert_type(spec_part, [1,]))
- self.assertEqual([1,2], convert_type(spec_part, [1,2]))
- self.assertEqual([1,2], convert_type(spec_part, ['1', '2']))
-
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, 1.1)
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, True)
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "a")
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "a", "b" ])
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "1", "b" ])
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
-
- spec_part = find_spec_part(config_spec, "value6")
- self.assertEqual({}, convert_type(spec_part, '{}'))
- self.assertEqual({ 'v61': 'a' }, convert_type(spec_part, '{ \'v61\': \'a\' }'))
-
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, 1.1)
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, True)
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "a")
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "1")
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "a", "b" ])
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "1", "b" ])
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "\"{ \"a\": 1 }\"")
-
- spec_part = find_spec_part(config_spec, "value7")
- self.assertEqual(['1', '2'], convert_type(spec_part, '1, 2'))
- self.assertEqual(['1', '2', '3'], convert_type(spec_part, '1 2 3'))
- self.assertEqual(['1', '2', '3','4'], convert_type(spec_part, '1 2 3, 4'))
- self.assertEqual([1], convert_type(spec_part, [1,]))
- self.assertEqual([1,2], convert_type(spec_part, [1,2]))
- self.assertEqual(['1','2'], convert_type(spec_part, ['1', '2']))
-
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, 1.1)
- self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, True)
- self.assertEqual(['a'], convert_type(spec_part, "a"))
- self.assertEqual(['a', 'b'], convert_type(spec_part, ["a", "b" ]))
- self.assertEqual([1, 'b'], convert_type(spec_part, [1, "b" ]))
-
- def test_find_spec_part(self):
- config_spec = self.cd.get_module_spec().get_config_spec()
- spec_part = find_spec_part(config_spec, "item1")
- self.assertEqual({'item_name': 'item1', 'item_type': 'integer', 'item_optional': False, 'item_default': 1, }, spec_part)
- spec_part = find_spec_part(config_spec, "/item1")
- self.assertEqual({'item_name': 'item1', 'item_type': 'integer', 'item_optional': False, 'item_default': 1, }, spec_part)
- self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, config_spec, "no_such_item")
- self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, config_spec, "no_such_item/multilevel")
- self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, config_spec, "item6/multilevel")
- self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, 1, "item6/multilevel")
- spec_part = find_spec_part(config_spec, "item6/value1")
- self.assertEqual({'item_name': 'value1', 'item_type': 'string', 'item_optional': True, 'item_default': 'default'}, spec_part)
-
- # make sure the returned data is a copy
- spec_part['item_default'] = 'foo'
- self.assertNotEqual(spec_part, find_spec_part(config_spec, "item6/value1"))
-
- def test_find_spec_part_lists(self):
- # A few specific tests for list data
- module_spec = isc.config.module_spec_from_file(self.data_path +
- os.sep +
- "spec31.spec")
- config_spec = module_spec.get_config_spec()
-
- expected_spec_part = {'item_name': 'number',
- 'item_type': 'integer',
- 'item_default': 1,
- 'item_optional': False}
-
- # First a check for a correct fetch
- spec_part = find_spec_part(config_spec,
- "/first_list_items[0]/second_list_items[1]/"
- "map_element/list1[1]/list2[1]")
- self.assertEqual(expected_spec_part, spec_part)
-
- # Leaving out an index should fail by default
- self.assertRaises(isc.cc.data.DataNotFoundError,
- find_spec_part, config_spec,
- "/first_list_items[0]/second_list_items/"
- "map_element/list1[1]/list2[1]")
-
- # But not for the last element
- spec_part = find_spec_part(config_spec,
- "/first_list_items[0]/second_list_items[1]/"
- "map_element/list1[1]/list2")
- self.assertEqual(expected_spec_part, spec_part)
-
- # And also not if strict_identifier is false (third argument)
- spec_part = find_spec_part(config_spec,
- "/first_list_items[0]/second_list_items/"
- "map_element/list1[1]/list2[1]", False)
- self.assertEqual(expected_spec_part, spec_part)
-
-
- def test_spec_name_list(self):
- name_list = spec_name_list(self.cd.get_module_spec().get_config_spec())
- self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5', 'item6'], name_list)
- name_list = spec_name_list(self.cd.get_module_spec().get_config_spec(), "", True)
- self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5', 'item6/value1', 'item6/value2'], name_list)
- spec_part = find_spec_part(self.cd.get_module_spec().get_config_spec(), "item6")
- name_list = spec_name_list(spec_part, "item6", True)
- self.assertEqual(['item6/value1', 'item6/value2'], name_list)
- spec_part = find_spec_part(self.cd.get_module_spec().get_config_spec(), "item6")
- name_list = spec_name_list(spec_part, "item6", True)
- self.assertEqual(['item6/value1', 'item6/value2'], name_list)
-
- config_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec22.spec").get_config_spec()
- spec_part = find_spec_part(config_spec, "value9")
- name_list = spec_name_list(spec_part, "value9", True)
- self.assertEqual(['value9/v91', 'value9/v92/v92a', 'value9/v92/v92b'], name_list)
-
- name_list = spec_name_list({ "myModule": config_spec }, "", False)
- self.assertEqual(['myModule/'], name_list)
- name_list = spec_name_list({ "myModule": config_spec }, "", True)
- self.assertEqual(['myModule/', 'myModule/value1', 'myModule/value2', 'myModule/value3', 'myModule/value4', 'myModule/value5', 'myModule/value6/v61', 'myModule/value6/v62', 'myModule/value7', 'myModule/value8', 'myModule/value9/v91', 'myModule/value9/v92/v92a', 'myModule/value9/v92/v92b'], name_list)
-
- self.assertRaises(ConfigDataError, spec_name_list, 1)
- self.assertRaises(ConfigDataError, spec_name_list, [ 'a' ])
-
- # Test one with type any as well
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec40.spec")
- spec_part = module_spec.get_config_spec()
- name_list = spec_name_list(module_spec.get_config_spec())
- self.assertEqual(['item1', 'item2', 'item3'], name_list)
-
- # item3 itself is 'empty'
- spec_part = find_spec_part(spec_part, 'item3')
- name_list = spec_name_list(spec_part)
- self.assertEqual([], name_list)
-
-
- def test_init(self):
- self.assertRaises(ConfigDataError, ConfigData, "asdf")
-
- def test_get_value(self):
- value, default = self.cd.get_value("item1")
- self.assertEqual(1, value)
- self.assertEqual(True, default)
- value, default = self.cd.get_value("item2")
- self.assertEqual(1.1, value)
- self.assertEqual(True, default)
- value, default = self.cd.get_value("item3")
- self.assertEqual(True, value)
- self.assertEqual(True, default)
- value, default = self.cd.get_value("item4")
- self.assertEqual("test", value)
- self.assertEqual(True, default)
- value, default = self.cd.get_value("item5")
- self.assertEqual(["a", "b"], value)
- self.assertEqual(True, default)
- value, default = self.cd.get_value("item6")
- self.assertEqual({}, value)
- self.assertEqual(True, default)
- self.assertRaises(isc.cc.data.DataNotFoundError, self.cd.get_value, "no_such_item")
- value, default = self.cd.get_value("item6/value2")
- self.assertEqual(None, value)
- self.assertEqual(False, default)
- self.assertRaises(isc.cc.data.DataNotFoundError, self.cd.get_value, "item6/no_such_item")
-
- def test_get_default_value(self):
- self.assertEqual(1, self.cd.get_default_value("item1"))
- self.assertEqual('default', self.cd.get_default_value("item6/value1"))
- self.assertEqual(None, self.cd.get_default_value("item6/value2"))
-
- # set some local values to something else, and see if we
- # still get the default
- self.cd.set_local_config({"item1": 2, "item6": { "value1": "asdf" } })
-
- self.assertEqual((2, False), self.cd.get_value("item1"))
- self.assertEqual(1, self.cd.get_default_value("item1"))
- self.assertEqual(('asdf', False), self.cd.get_value("item6/value1"))
- self.assertEqual('default', self.cd.get_default_value("item6/value1"))
-
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.cd.get_default_value,
- "does_not_exist/value1")
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.cd.get_default_value,
- "item6/doesnotexist")
-
- def test_set_local_config(self):
- self.cd.set_local_config({"item1": 2})
- value, default = self.cd.get_value("item1")
- self.assertEqual(2, value)
- self.assertEqual(False, default)
-
- def test_get_local_config(self):
- local_config = self.cd.get_local_config()
- self.assertEqual({}, local_config)
- my_config = { "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ] }
- self.cd.set_local_config(my_config)
- self.assertEqual(my_config, self.cd.get_local_config())
-
- def test_get_item_list(self):
- name_list = self.cd.get_item_list()
- self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5', 'item6'], name_list)
- name_list = self.cd.get_item_list("", True)
- self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5', 'item6/value1', 'item6/value2'], name_list)
- name_list = self.cd.get_item_list("item6", False)
- self.assertEqual(['item6/value1', 'item6/value2'], name_list)
-
- def test_get_full_config(self):
- full_config = self.cd.get_full_config()
- self.assertEqual({ "item1": 1, "item2": 1.1, "item3": True, "item4": "test", "item5": ['a', 'b'], "item6/value1": 'default', 'item6/value2': None}, full_config)
- my_config = { "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ] }
- self.cd.set_local_config(my_config)
- full_config = self.cd.get_full_config()
- self.assertEqual({ "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ], "item6/value1": 'default', 'item6/value2': None}, full_config)
-
-class TestMultiConfigData(unittest.TestCase):
- def setUp(self):
- if 'CONFIG_TESTDATA_PATH' in os.environ:
- self.data_path = os.environ['CONFIG_TESTDATA_PATH']
- else:
- self.data_path = "../../../testdata"
- self.mcd = MultiConfigData()
-
- def test_init(self):
- self.assertEqual({}, self.mcd._specifications)
- self.assertEqual({}, self.mcd._current_config)
- self.assertEqual({}, self.mcd._local_changes)
-
- def test_set_remove_specification(self):
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
- self.assertFalse(self.mcd.have_specification(module_spec.get_module_name()))
- self.mcd.set_specification(module_spec)
- self.assertTrue(self.mcd.have_specification(module_spec.get_module_name()))
- self.assertIn(module_spec.get_module_name(), self.mcd._specifications)
- self.assertEqual(module_spec, self.mcd._specifications[module_spec.get_module_name()])
- self.assertRaises(ConfigDataError, self.mcd.set_specification, "asdf")
- self.mcd.remove_specification(module_spec.get_module_name())
- self.assertFalse(self.mcd.have_specification(module_spec.get_module_name()))
-
- def test_clear_specifications(self):
- self.assertEqual(0, len(self.mcd._specifications))
- module_spec = isc.config.module_spec_from_file(self.data_path +
- os.sep +
- "spec1.spec")
- self.mcd.set_specification(module_spec)
- self.assertEqual(1, len(self.mcd._specifications))
- self.mcd.clear_specifications()
- self.assertEqual(0, len(self.mcd._specifications))
-
- def test_get_module_spec(self):
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
- self.mcd.set_specification(module_spec)
- module_spec2 = self.mcd.get_module_spec(module_spec.get_module_name())
- self.assertEqual(module_spec, module_spec2)
- module_spec3 = self.mcd.get_module_spec("no_such_module")
- self.assertEqual(None, module_spec3)
-
- def test_find_spec_part(self):
- spec_part = self.mcd.find_spec_part("Spec2/item1")
- self.assertEqual(None, spec_part)
- spec_part = self.mcd.find_spec_part("/Spec2/item1")
- self.assertEqual(None, spec_part)
- module_spec = isc.config.module_spec_from_file(self.data_path +
- os.sep + "spec2.spec")
- self.mcd.set_specification(module_spec)
- spec_part = self.mcd.find_spec_part("Spec2/item1")
- self.assertEqual({'item_name': 'item1', 'item_type': 'integer',
- 'item_optional': False, 'item_default': 1, },
- spec_part)
-
- # For lists, either the spec of the list itself, or the
- # spec for the list contents should be returned (the
- # latter when an index is given in the identifier)
- spec_part = self.mcd.find_spec_part("Spec2/item5")
- self.assertEqual({'item_default': ['a', 'b'],
- 'item_name': 'item5',
- 'item_optional': False,
- 'item_type': 'list',
- 'list_item_spec': {'item_default': '',
- 'item_name': 'list_element',
- 'item_optional': False,
- 'item_type': 'string'}},
- spec_part)
- spec_part = self.mcd.find_spec_part("Spec2/item5[0]")
- self.assertEqual({'item_default': '',
- 'item_name': 'list_element',
- 'item_optional': False,
- 'item_type': 'string'},
- spec_part)
-
-
- def test_find_spec_part_nested(self):
- # Check that find_spec_part works for nested lists
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec30.spec")
- self.mcd.set_specification(module_spec)
- spec_part = self.mcd.find_spec_part("/lists/first_list_items[0]/second_list_items[1]/final_element")
- self.assertEqual({'item_name': 'final_element', 'item_type': 'string', 'item_default': 'hello', 'item_optional': False}, spec_part)
- spec_part = self.mcd.find_spec_part("/BAD_NAME/first_list_items[0]/second_list_items[1]/final_element")
- self.assertEqual(None, spec_part)
-
- def test_find_spec_part_nested2(self):
- # Check that find_spec_part works for nested lists and maps
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec31.spec")
- self.mcd.set_specification(module_spec)
- spec_part = self.mcd.find_spec_part("/lists/first_list_items[0]/second_list_items[1]/map_element/list1[1]/list2[2]")
- self.assertEqual({"item_name": "number", "item_type": "integer", "item_optional": False, "item_default": 1}, spec_part)
-
- spec_part = self.mcd.find_spec_part("/DOESNOTEXIST")
- self.assertEqual(None, spec_part)
- spec_part = self.mcd.find_spec_part("/lists/DOESNOTEXIST")
- self.assertEqual(None, spec_part)
- spec_part = self.mcd.find_spec_part("/lists/first_list_items[0]/DOESNOTEXIST")
- self.assertEqual(None, spec_part)
- spec_part = self.mcd.find_spec_part("/lists/first_list_items[0]/second_list_items[1]/DOESNOTEXIST")
- self.assertEqual(None, spec_part)
- spec_part = self.mcd.find_spec_part("/lists/first_list_items[0]/second_list_items[1]/map_element/DOESNOTEXIST")
- self.assertEqual(None, spec_part)
- spec_part = self.mcd.find_spec_part("/lists/first_list_items[0]/second_list_items[1]/map_element/list1[1]/DOESNOTEXIST")
- self.assertEqual(None, spec_part)
- spec_part = self.mcd.find_spec_part("/lists/first_list_items[0]/second_list_items[1]/map_element/list1[1]/list2[1]/DOESNOTEXIST")
- self.assertEqual(None, spec_part)
-
- def test_get_current_config(self):
- cf = { 'module1': { 'item1': 2, 'item2': True } }
- self.mcd._set_current_config(cf)
- self.assertEqual(cf, self.mcd.get_current_config())
-
- def test_get_local_changes(self):
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
- self.mcd.set_specification(module_spec)
- local_changes = self.mcd.get_local_changes()
- self.assertEqual({}, local_changes)
- self.mcd.set_value("Spec2/item1", 2)
- local_changes = self.mcd.get_local_changes()
- self.assertEqual({"Spec2": { "item1": 2}}, local_changes)
-
- def test_set_local_changes(self):
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
- self.mcd.set_specification(module_spec)
- self.assertEqual({}, self.mcd.get_local_changes())
- new_local_changes = {"Spec2": { "item1": 2}}
- self.mcd.set_local_changes(new_local_changes)
- self.assertEqual(new_local_changes, self.mcd.get_local_changes())
-
- def test_clear_local_changes(self):
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
- self.mcd.set_specification(module_spec)
- self.mcd.set_value("Spec2/item1", 2)
- self.mcd.clear_local_changes()
- local_changes = self.mcd.get_local_changes()
- self.assertEqual({}, local_changes)
- pass
-
- def test_get_local_value(self):
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
- self.mcd.set_specification(module_spec)
- value = self.mcd.get_local_value("Spec2/item1")
- self.assertEqual(None, value)
- self.mcd.set_value("Spec2/item1", 2)
- value = self.mcd.get_local_value("Spec2/item1")
- self.assertEqual(2, value)
-
- def test_get_current_value(self):
- value = self.mcd.get_current_value("Spec2/item1")
- self.assertEqual(None, value)
- self.mcd._current_config = { "Spec2": { "item1": 3 } }
- value = self.mcd.get_current_value("Spec2/item1")
- self.assertEqual(3, value)
- pass
-
- def test_get_default_value(self):
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
- self.mcd.set_specification(module_spec)
- value = self.mcd.get_default_value("Spec2/item1")
- self.assertEqual(1, value)
- value = self.mcd.get_default_value("/Spec2/item1")
- self.assertEqual(1, value)
- value = self.mcd.get_default_value("Spec2/item5[0]")
- self.assertEqual('a', value)
- value = self.mcd.get_default_value("Spec2/item5[1]")
- self.assertEqual('b', value)
- value = self.mcd.get_default_value("Spec2/item5[5]")
- self.assertEqual(None, value)
- value = self.mcd.get_default_value("Spec2/item5[0][1]")
- self.assertEqual(None, value)
- value = self.mcd.get_default_value("Spec2/item6/value1")
- self.assertEqual('default', value)
- value = self.mcd.get_default_value("Spec2/item6/value2")
- self.assertEqual(None, value)
- value = self.mcd.get_default_value("Spec2/no_such_item/asdf")
- self.assertEqual(None, value)
-
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec32.spec")
- self.mcd.set_specification(module_spec)
- value = self.mcd.get_default_value("Spec32/named_set_item")
- self.assertEqual({ 'a': 1, 'b': 2}, value)
- value = self.mcd.get_default_value("Spec32/named_set_item/a")
- self.assertEqual(1, value)
- value = self.mcd.get_default_value("Spec32/named_set_item/b")
- self.assertEqual(2, value)
- value = self.mcd.get_default_value("Spec32/named_set_item/no_such_item")
- self.assertEqual(None, value)
- # Check that top-level default value works when named set contains list
- # (issue #2114)
- value = self.mcd.get_default_value("Spec32/named_set_item3/values[2]")
- self.assertEqual(3, value)
- self.assertRaises(IndexError, self.mcd.get_default_value,
- "Spec32/named_set_item3/values[5]")
-
- def test_get_value(self):
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
- self.mcd.set_specification(module_spec)
- self.mcd.set_value("Spec2/item1", 2)
-
- value, status = self.mcd.get_value("Spec2/item1")
- self.assertEqual(2, value)
- self.assertEqual(MultiConfigData.LOCAL, status)
-
- value, status = self.mcd.get_value("Spec2/item2")
- self.assertEqual(1.1, value)
- self.assertEqual(MultiConfigData.DEFAULT, status)
-
- self.mcd._current_config = { "Spec2": { "item3": False } }
-
- value, status = self.mcd.get_value("Spec2/item3")
- self.assertEqual(False, value)
- self.assertEqual(MultiConfigData.CURRENT, status)
-
- value, status = self.mcd.get_value("Spec2/no_such_item")
- self.assertEqual(None, value)
- self.assertEqual(MultiConfigData.NONE, status)
-
- value, status = self.mcd.get_value("Spec2/item5")
- self.assertEqual(['a', 'b'], value)
- self.assertEqual(MultiConfigData.DEFAULT, status)
-
- value, status = self.mcd.get_value("Spec2/item5[0]")
- self.assertEqual("a", value)
- self.assertEqual(MultiConfigData.DEFAULT, status)
-
- value, status = self.mcd.get_value("Spec2/item5[0]", False)
- self.assertEqual(None, value)
- self.assertEqual(MultiConfigData.NONE, status)
-
- value, status = self.mcd.get_value("Spec2/item5[1]")
- self.assertEqual("b", value)
- self.assertEqual(MultiConfigData.DEFAULT, status)
-
-
- def test_get_value_maps(self):
- maps = self.mcd.get_value_maps()
- self.assertEqual([], maps)
-
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
- self.mcd.set_specification(module_spec)
-
- expected = [{'default': False,
- 'type': 'module',
- 'name': 'Spec1',
- 'value': None,
- 'modified': False}]
-
- maps = self.mcd.get_value_maps()
- self.assertEqual(expected, maps)
-
- maps = self.mcd.get_value_maps("/")
- self.assertEqual(expected, maps)
-
- maps = self.mcd.get_value_maps('Spec2')
- self.assertEqual([], maps)
- maps = self.mcd.get_value_maps('Spec1')
- self.assertEqual([], maps)
- self.mcd.remove_specification("Spec1")
- self.mcd.remove_specification("foo")
-
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
- self.mcd.set_specification(module_spec)
- maps = self.mcd.get_value_maps()
- self.assertEqual([{'default': False, 'type': 'module', 'name': 'Spec2', 'value': None, 'modified': False}], maps)
- self.mcd._set_current_config({ "Spec2": { "item1": 2 } })
- self.mcd.set_value("Spec2/item3", False)
- maps = self.mcd.get_value_maps("/Spec2")
- self.assertEqual([{'default': False, 'type': 'integer', 'name': 'Spec2/item1', 'value': 2, 'modified': False},
- {'default': True, 'type': 'real', 'name': 'Spec2/item2', 'value': 1.1, 'modified': False},
- {'default': False, 'type': 'boolean', 'name': 'Spec2/item3', 'value': False, 'modified': True},
- {'default': True, 'type': 'string', 'name': 'Spec2/item4', 'value': 'test', 'modified': False},
- {'default': True, 'type': 'list', 'name': 'Spec2/item5', 'value': ['a', 'b'], 'modified': False},
- {'default': True, 'type': 'string', 'name': 'Spec2/item6/value1', 'value': 'default', 'modified': False},
- {'default': False, 'type': 'integer', 'name': 'Spec2/item6/value2', 'value': None, 'modified': False}], maps)
- maps = self.mcd.get_value_maps("Spec2")
- self.assertEqual([{'default': False, 'type': 'integer', 'name': 'Spec2/item1', 'value': 2, 'modified': False},
- {'default': True, 'type': 'real', 'name': 'Spec2/item2', 'value': 1.1, 'modified': False},
- {'default': False, 'type': 'boolean', 'name': 'Spec2/item3', 'value': False, 'modified': True},
- {'default': True, 'type': 'string', 'name': 'Spec2/item4', 'value': 'test', 'modified': False},
- {'default': True, 'type': 'list', 'name': 'Spec2/item5', 'value': ['a', 'b'], 'modified': False},
- {'default': True, 'type': 'string', 'name': 'Spec2/item6/value1', 'value': 'default', 'modified': False},
- {'default': False, 'type': 'integer', 'name': 'Spec2/item6/value2', 'value': None, 'modified': False}], maps)
- maps = self.mcd.get_value_maps("/Spec2/item5")
- self.assertEqual([{'default': True, 'type': 'string', 'name': 'Spec2/item5[0]', 'value': 'a', 'modified': False},
- {'default': True, 'type': 'string', 'name': 'Spec2/item5[1]', 'value': 'b', 'modified': False}], maps)
- maps = self.mcd.get_value_maps("/Spec2/item5[0]")
- self.assertEqual([{'default': True, 'modified': False, 'name': 'Spec2/item5[0]', 'type': 'string', 'value': 'a'}], maps)
- maps = self.mcd.get_value_maps("/Spec2/item1")
- self.assertEqual([{'default': False, 'type': 'integer', 'name': 'Spec2/item1', 'value': 2, 'modified': False}], maps)
- maps = self.mcd.get_value_maps("/Spec2/item2")
- self.assertEqual([{'default': True, 'type': 'real', 'name': 'Spec2/item2', 'value': 1.1, 'modified': False}], maps)
- maps = self.mcd.get_value_maps("/Spec2/item3")
- self.assertEqual([{'default': False, 'type': 'boolean', 'name': 'Spec2/item3', 'value': False, 'modified': True}], maps)
- maps = self.mcd.get_value_maps("/Spec2/item4")
- self.assertEqual([{'default': True, 'type': 'string', 'name': 'Spec2/item4', 'value': 'test', 'modified': False}], maps)
-
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec24.spec")
- self.mcd.set_specification(module_spec)
- # optional list item that is not set should return as empty list
- maps = self.mcd.get_value_maps("/Spec24/item", 4)
- self.assertEqual([{'default': False, 'type': 'list', 'name': 'Spec24/item', 'value': [], 'modified': False}], maps)
-
- self.mcd._set_current_config({ "Spec24": { "item": [] } })
- maps = self.mcd.get_value_maps("/Spec24/item")
- self.assertEqual([{'default': False, 'modified': False, 'name': 'Spec24/item', 'type': 'list', 'value': []}], maps)
-
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec22.spec")
- self.mcd.set_specification(module_spec)
- expected = [{'default': True,
- 'modified': False,
- 'name': 'Spec22/value9/v91',
- 'type': 'string',
- 'value': 'def'},
- {'default': True,
- 'modified': False,
- 'name': 'Spec22/value9/v92/v92a',
- 'type': 'string',
- 'value': 'Hello'
- },
- {'default': True,
- 'modified': False,
- 'name': 'Spec22/value9/v92/v92b',
- 'type': 'integer',
- 'value': 56176
- }
- ]
- maps = self.mcd.get_value_maps("/Spec22/value9")
- self.assertEqual(expected, maps)
-
- # A slash at the end should not produce different output
- maps = self.mcd.get_value_maps("/Spec22/value9/")
- self.assertEqual(expected, maps)
-
- # A slash at the end should not produce different output with
- # indices either
- expected2 = [{'default': True,
- 'type': 'integer',
- 'name': 'Spec22/value5[1]',
- 'value': 'b',
- 'modified': False}]
- maps = self.mcd.get_value_maps("/Spec22/value5[1]/")
- self.assertEqual(expected2, maps)
-
- def test_get_value_maps_named_set(self):
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec32.spec")
- self.mcd.set_specification(module_spec)
- maps = self.mcd.get_value_maps()
- self.assertEqual([{'default': False, 'type': 'module',
- 'name': 'Spec32', 'value': None,
- 'modified': False}], maps)
- maps = self.mcd.get_value_maps("/Spec32/named_set_item")
- self.assertEqual(len(maps), 2)
- self.assertIn({'default': True, 'type': 'integer',
- 'name': 'Spec32/named_set_item/a',
- 'value': 1, 'modified': False},
- maps)
- self.assertIn({'default': True, 'type': 'integer',
- 'name': 'Spec32/named_set_item/b',
- 'value': 2, 'modified': False},
- maps)
- maps = self.mcd.get_value_maps("/Spec32/named_set_item/a")
- self.assertEqual([{'default': True, 'type': 'integer',
- 'name': 'Spec32/named_set_item/a',
- 'value': 1, 'modified': False}], maps)
- maps = self.mcd.get_value_maps("/Spec32/named_set_item/b")
- self.assertEqual([{'default': True, 'type': 'integer',
- 'name': 'Spec32/named_set_item/b',
- 'value': 2, 'modified': False}], maps)
-
- def test_set_value(self):
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
- self.mcd.set_specification(module_spec)
- self.mcd.set_value("Spec2/item1", 2)
- self.assertRaises(isc.cc.data.DataTypeError,
- self.mcd.set_value, "Spec2/item1", "asdf")
-
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.mcd.set_value, "Spec2/no_such_item", 4)
-
- self.mcd.set_value("Spec2/item5[0]", "c")
- value, status = self.mcd.get_value("Spec2/item5[0]")
- self.assertEqual(value, "c")
- self.assertEqual(MultiConfigData.LOCAL, status)
-
- self.assertRaises(isc.cc.data.DataTypeError, self.mcd.set_value, "Spec2/item5[a]", "asdf")
-
-
- def test_unset(self):
- """
- Test the unset command works.
- """
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
- self.mcd.set_specification(module_spec)
- self.mcd.set_specification(module_spec)
- value, status = self.mcd.get_value("Spec2/item1")
- # This is the default first
- self.assertEqual(1, value)
- self.assertEqual(MultiConfigData.DEFAULT, status)
- # Unseting a default item does nothing.
- self.mcd.unset("Spec2/item1")
- value, status = self.mcd.get_value("Spec2/item1")
- # This should be the default
- self.assertEqual(1, value)
- self.assertEqual(MultiConfigData.DEFAULT, status)
- # Set it to something else
- self.mcd.set_value("Spec2/item1", 42)
- value, status = self.mcd.get_value("Spec2/item1")
- self.assertEqual(42, value)
- self.assertEqual(MultiConfigData.LOCAL, status)
- # Try to unset it
- self.mcd.unset("Spec2/item1")
- value, status = self.mcd.get_value("Spec2/item1")
- # This should be the default
- self.assertEqual(1, value)
- self.assertEqual(MultiConfigData.DEFAULT, status)
- # Unset a nonexisting item. Should raise.
- self.assertRaises(isc.cc.data.DataNotFoundError, self.mcd.unset, "Spec2/doesnotexist")
-
- def test_get_config_item_list(self):
- # Test get_config_item_list(), which returns a list of the config
- # items in a specification.
- config_items = self.mcd.get_config_item_list()
- self.assertEqual([], config_items)
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
- self.mcd.set_specification(module_spec)
- config_items = self.mcd.get_config_item_list()
- self.assertEqual(['Spec2'], config_items)
- config_items = self.mcd.get_config_item_list(None, False)
- self.assertEqual(['Spec2'], config_items)
- config_items = self.mcd.get_config_item_list(None, True)
- self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3',
- 'Spec2/item4', 'Spec2/item5', 'Spec2/item6/value1',
- 'Spec2/item6/value2'], config_items)
- config_items = self.mcd.get_config_item_list("Spec2", True)
- self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3',
- 'Spec2/item4', 'Spec2/item5[0]', 'Spec2/item5[1]',
- 'Spec2/item6/value1', 'Spec2/item6/value2'],
- config_items)
- config_items = self.mcd.get_config_item_list("Spec2")
- self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3',
- 'Spec2/item4', 'Spec2/item5[0]', 'Spec2/item5[1]',
- 'Spec2/item6'], config_items)
- config_items = self.mcd.get_config_item_list("/Spec2")
- self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3',
- 'Spec2/item4', 'Spec2/item5[0]', 'Spec2/item5[1]',
- 'Spec2/item6'], config_items)
- config_items = self.mcd.get_config_item_list("Spec2", True)
- self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3',
- 'Spec2/item4', 'Spec2/item5[0]', 'Spec2/item5[1]',
- 'Spec2/item6/value1', 'Spec2/item6/value2'],
- config_items)
-
- # When lists are empty, it should only show the name
- self.mcd.set_value('Spec2/item5', [])
- config_items = self.mcd.get_config_item_list("Spec2", True)
- self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3',
- 'Spec2/item4', 'Spec2/item5', 'Spec2/item6/value1',
- 'Spec2/item6/value2'], config_items)
-
- # Also if the list is None (optional value and no default)
- module_spec = isc.config.module_spec_from_file(self.data_path
- + os.sep
- + "spec42.spec")
- self.mcd.set_specification(module_spec)
- config_items = self.mcd.get_config_item_list("Spec42", True)
- self.assertEqual(['Spec42/list_item'], config_items)
-
- def test_is_named_set(self):
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec32.spec")
- self.mcd.set_specification(module_spec)
- spec_part = self.mcd.find_spec_part("Spec32/named_set_item")
- self.assertTrue(spec_part_is_named_set(spec_part))
-
- def test_get_config_item_list_named_set(self):
- config_items = self.mcd.get_config_item_list()
- self.assertEqual([], config_items)
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec32.spec")
- self.mcd.set_specification(module_spec)
- config_items = self.mcd.get_config_item_list()
- self.assertEqual(['Spec32'], config_items)
- config_items = self.mcd.get_config_item_list(None, False)
- self.assertEqual(['Spec32'], config_items)
- config_items = self.mcd.get_config_item_list(None, True)
- self.assertEqual(['Spec32/named_set_item', 'Spec32/named_set_item2',
- 'Spec32/named_set_item3', 'Spec32/named_set_item4'],
- config_items)
- self.mcd.set_value('Spec32/named_set_item', { "aaaa": 4, "aabb": 5,
- "bbbb": 6})
- config_items = self.mcd.get_config_item_list("/Spec32/named_set_item",
- True)
- self.assertEqual(len(config_items), 3)
- self.assertIn('Spec32/named_set_item/aaaa', config_items)
- self.assertIn('Spec32/named_set_item/aabb', config_items)
- self.assertIn('Spec32/named_set_item/bbbb', config_items)
-
- self.mcd.set_value('Spec32/named_set_item', {})
- config_items = self.mcd.get_config_item_list("/Spec32/named_set_item",
- True)
- self.assertEqual(['Spec32/named_set_item'], config_items)
-
- self.mcd.set_value('Spec32/named_set_item4', { "a": { "aa": 4 } } )
- config_items = self.mcd.get_config_item_list("/Spec32/named_set_item4",
- True)
- self.assertEqual(['Spec32/named_set_item4/a/'], config_items)
- config_items = self.mcd.get_config_item_list("/Spec32/named_set_item4/a",
- True)
- self.assertEqual(['Spec32/named_set_item4/a/aa'], config_items)
-
- def test_set_named_set_nonlocal(self):
- # Test whether a default named set is copied to local if a subitem
- # is changed, and that other items in the set do not get lost
- module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + 'spec32.spec')
- self.mcd.set_specification(module_spec)
- value, status = self.mcd.get_value('Spec32/named_set_item')
- self.assertEqual({'a': 1, 'b': 2}, value)
- self.assertEqual(MultiConfigData.DEFAULT, status)
-
- self.mcd.set_value('Spec32/named_set_item/b', 3)
- value, status = self.mcd.get_value('Spec32/named_set_item')
- self.assertEqual({'a': 1, 'b': 3}, value)
- self.assertEqual(MultiConfigData.LOCAL, status)
-
-if __name__ == '__main__':
- unittest.main()
-
+++ /dev/null
-#! /bin/sh
-
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
-export PYTHON_EXEC
-
-CONFIG_PATH=@abs_top_srcdir@/src/lib/python/isc/config/tests
-
-PYTHONPATH=@abs_top_srcdir@/src/lib/python
-export PYTHONPATH
-
-CONFIG_TESTDATA_PATH=@abs_top_srcdir@/src/lib/config/testdata
-export CONFIG_TESTDATA_PATH
-
-cd ${BIND10_PATH}
-${PYTHON_EXEC} -O ${CONFIG_PATH}/config_data_test.py $*
-
-${PYTHON_EXEC} -O ${CONFIG_PATH}/module_spec_test.py $*
-
-${PYTHON_EXEC} -O ${CONFIG_PATH}/ccsession_test.py $*
-
-${PYTHON_EXEC} -O ${CONFIG_PATH}/cfgmgr_test.py $*
+++ /dev/null
-# Copyright (C) 2009 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Tests for the module_spec module
-#
-
-import unittest
-import os
-from isc.config import ModuleSpec, ModuleSpecError
-import isc.cc.data
-
-class TestModuleSpec(unittest.TestCase):
-
- def setUp(self):
- if 'CONFIG_TESTDATA_PATH' in os.environ:
- self.data_path = os.environ['CONFIG_TESTDATA_PATH']
- else:
- self.data_path = "../../../testdata"
-
- def spec_file(self, filename):
- return(self.data_path + os.sep + filename)
-
- def read_spec_file(self, filename):
- return isc.config.module_spec_from_file(self.spec_file(filename))
-
- def spec1(self, dd):
- module_spec = dd.get_full_spec()
- self.assertIn('module_name', module_spec)
- self.assertEqual(module_spec['module_name'], "Spec1")
-
- def test_open_file_name(self):
- dd = self.read_spec_file("spec1.spec")
- self.spec1(dd)
-
- def test_open_file_obj(self):
- with open(self.spec_file("spec1.spec")) as file1:
- dd = isc.config.module_spec_from_file(file1)
- self.spec1(dd)
-
- def test_open_bad_file_obj(self):
- self.assertRaises(ModuleSpecError, isc.config.module_spec_from_file, 1)
- # contains single quotes which json parser does not accept
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec_from_file, self.spec_file("spec28.spec"), False)
- my_spec_file = open(self.spec_file("spec28.spec"))
- self.assertRaises(ModuleSpecError, isc.config.module_spec_from_file, my_spec_file, False)
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec_from_file, self.spec_file("does_not_exist"), False)
-
- def test_bad_specfiles(self):
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec3.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec4.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec5.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec6.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec7.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec8.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec9.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec10.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec11.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec12.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec13.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec14.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec15.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec16.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec17.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec18.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec19.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec20.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec21.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec26.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec34.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec35.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec36.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec37.spec")
- self.assertRaises(ModuleSpecError, self.read_spec_file, "spec38.spec")
-
- def validate_data(self, specfile_name, datafile_name):
- dd = self.read_spec_file(specfile_name);
- with open(self.spec_file(datafile_name)) as data_file:
- data_str = data_file.read()
- data = isc.cc.data.parse_value_str(data_str)
- return dd.validate_config(True, data)
-
- def test_data_validation(self):
- self.assertEqual(True, self.validate_data("spec22.spec", "data22_1.data"))
- self.assertEqual(False, self.validate_data("spec22.spec", "data22_2.data"))
- self.assertEqual(False, self.validate_data("spec22.spec", "data22_3.data"))
- self.assertEqual(False, self.validate_data("spec22.spec", "data22_4.data"))
- self.assertEqual(False, self.validate_data("spec22.spec", "data22_5.data"))
- self.assertEqual(True, self.validate_data("spec22.spec", "data22_6.data"))
- self.assertEqual(True, self.validate_data("spec22.spec", "data22_7.data"))
- self.assertEqual(False, self.validate_data("spec22.spec", "data22_8.data"))
- self.assertEqual(True, self.validate_data("spec32.spec", "data32_1.data"))
- self.assertEqual(False, self.validate_data("spec32.spec", "data32_2.data"))
- self.assertEqual(False, self.validate_data("spec32.spec", "data32_3.data"))
-
- def validate_command_params(self, specfile_name, datafile_name, cmd_name):
- dd = self.read_spec_file(specfile_name);
- with open(self.spec_file(datafile_name)) as data_file:
- data_str = data_file.read()
- params = isc.cc.data.parse_value_str(data_str)
- return dd.validate_command(cmd_name, params)
-
- def test_command_validation(self):
- # tests for a command that doesn't take an argument
- self.assertEqual(True, self.read_spec_file("spec2.spec").validate_command("shutdown", None));
- self.assertEqual(False, self.read_spec_file("spec2.spec").validate_command("shutdown", '{"val": 1}'));
- self.assertEqual(True, self.validate_command_params("spec27.spec", "data22_1.data", 'cmd1'))
- self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_2.data",'cmd1'))
- self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_3.data", 'cmd1'))
- self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_4.data", 'cmd1'))
- self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_5.data", 'cmd1'))
- self.assertEqual(True, self.validate_command_params("spec27.spec", "data22_6.data", 'cmd1'))
- self.assertEqual(True, self.validate_command_params("spec27.spec", "data22_7.data", 'cmd1'))
- self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_8.data", 'cmd1'))
- self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_8.data", 'cmd2'))
-
- def test_statistics_validation(self):
- def _validate_stat(specfile_name, datafile_name):
- dd = self.read_spec_file(specfile_name);
- with open(self.spec_file(datafile_name)) as data_file:
- data_str = data_file.read()
- data = isc.cc.data.parse_value_str(data_str)
- return dd.validate_statistics(True, data, [])
- self.assertFalse(self.read_spec_file("spec1.spec").validate_statistics(True, None, None));
- self.assertTrue(_validate_stat("spec33.spec", "data33_1.data"))
- self.assertFalse(_validate_stat("spec33.spec", "data33_2.data"))
- self.assertTrue(_validate_stat("spec41.spec", "data41_1.data"))
- self.assertFalse(_validate_stat("spec41.spec", "data41_2.data"))
-
- def test_init(self):
- self.assertRaises(ModuleSpecError, ModuleSpec, 1)
- module_spec = isc.config.module_spec_from_file(self.spec_file("spec1.spec"), False)
- self.spec1(module_spec)
-
- module_spec = isc.config.module_spec_from_file(self.spec_file("spec25.spec"), True)
- self.assertEqual("Spec25", module_spec.get_module_name())
- self.assertEqual("Just an empty module", module_spec.get_module_description())
-
- def test_str(self):
- module_spec = isc.config.module_spec_from_file(self.spec_file("spec1.spec"), False)
- self.assertEqual(module_spec.__str__(), "{'module_name': 'Spec1'}")
-
- def test_check_module_spec(self):
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check, 1)
-
- def test_check_command_spec(self):
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_command_spec, 1 )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_command_spec, [ 1 ] )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_command_spec,
- [ { 'command_name': 1,
- 'command_description': 'just for testing',
- 'command_args': [
- { 'item_name': 'arg1',
- 'item_type': 'string',
- 'item_optional': True
- }
- ]
- }
- ]
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_command_spec,
- [ { 'command_name': 'test_command',
- 'command_description': 1,
- 'command_args': [
- { 'item_name': 'arg1',
- 'item_type': 'string',
- 'item_optional': True
- }
- ]
- }
- ]
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_command_spec,
- [ { 'command_name': 'test_command',
- 'command_args': [ 1 ]
- }
- ]
- )
-
- def test_check_item_spec(self):
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec, 1 )
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': 1,
- 'item_type': "string",
- 'item_optional': False,
- 'item_default': "asdf"
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "an_item",
- 'item_type': "bad_type",
- 'item_optional': False,
- 'item_default': "asdf"
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "an_item",
- 'item_type': 1,
- 'item_optional': False,
- 'item_default': "asdf"
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "an_item",
- 'item_type': "string",
- 'item_optional': False,
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "an_item",
- 'item_type': "string",
- 'item_optional': 1,
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "an_item",
- 'item_type': "string",
- 'item_optional': False,
- 'item_default': 1
- }
- )
-
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "an_item",
- 'item_type': "list",
- 'item_optional': False,
- 'item_default': []
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "an_item",
- 'item_type': "list",
- 'item_optional': False,
- 'item_default': [],
- 'list_item_spec': 1
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "an_item",
- 'item_type': "map",
- 'item_optional': False,
- 'item_default': {}
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "an_item",
- 'item_type': "map",
- 'item_optional': False,
- 'item_default': {},
- 'map_item_spec': 1
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "an_item",
- 'item_type': "map",
- 'item_optional': False,
- 'item_default': {},
- 'map_item_spec': [ 1 ]
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "a_datetime",
- 'item_type': "string",
- 'item_optional': False,
- 'item_default': 1,
- 'item_format': "date-time"
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "a_date",
- 'item_type': "string",
- 'item_optional': False,
- 'item_default': 1,
- 'item_format': "date"
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "a_time",
- 'item_type': "string",
- 'item_optional': False,
- 'item_default': 1,
- 'item_format': "time"
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "a_datetime",
- 'item_type': "string",
- 'item_optional': False,
- 'item_default': "2011-05-27T19:42:57Z",
- 'item_format': "dummy-format"
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "a_date",
- 'item_type': "string",
- 'item_optional': False,
- 'item_default': "2011-05-27",
- 'item_format': "dummy-format"
- }
- )
-
- self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
- { 'item_name': "a_time",
- 'item_type': "string",
- 'item_optional': False,
- 'item_default': "19:42:57Z",
- 'item_format': "dummy-format"
- }
- )
-
- def test_check_format(self):
- self.assertTrue(isc.config.module_spec._check_format('2011-05-27T19:42:57Z', 'date-time'))
- self.assertTrue(isc.config.module_spec._check_format('2011-05-27', 'date'))
- self.assertTrue(isc.config.module_spec._check_format('19:42:57', 'time'))
- self.assertFalse(isc.config.module_spec._check_format('2011-05-27T19:42:57Z', 'dummy'))
- self.assertFalse(isc.config.module_spec._check_format('2011-05-27', 'dummy'))
- self.assertFalse(isc.config.module_spec._check_format('19:42:57', 'dummy'))
- self.assertFalse(isc.config.module_spec._check_format('2011-13-99T99:99:99Z', 'date-time'))
- self.assertFalse(isc.config.module_spec._check_format('2011-13-99', 'date'))
- self.assertFalse(isc.config.module_spec._check_format('99:99:99', 'time'))
- self.assertFalse(isc.config.module_spec._check_format('', 'date-time'))
- self.assertFalse(isc.config.module_spec._check_format(None, 'date-time'))
- self.assertFalse(isc.config.module_spec._check_format(None, None))
- # wrong date-time-type format not ending with "Z"
- self.assertFalse(isc.config.module_spec._check_format('2011-05-27T19:42:57', 'date-time'))
- # wrong date-type format ending with "T"
- self.assertFalse(isc.config.module_spec._check_format('2011-05-27T', 'date'))
- # wrong time-type format ending with "Z"
- self.assertFalse(isc.config.module_spec._check_format('19:42:57Z', 'time'))
-
- def test_validate_type(self):
- errors = []
- self.assertEqual(True, isc.config.module_spec._validate_type({ 'item_type': 'integer' }, 1, errors))
- self.assertEqual(False, isc.config.module_spec._validate_type({ 'item_type': 'integer' }, "a", None))
- self.assertEqual(False, isc.config.module_spec._validate_type({ 'item_type': 'integer' }, "a", errors))
- self.assertEqual(['a should be an integer'], errors)
-
- errors = []
- self.assertEqual(True, isc.config.module_spec._validate_type({ 'item_type': 'real' }, 1.1, errors))
- self.assertEqual(False, isc.config.module_spec._validate_type({ 'item_type': 'real' }, "a", None))
- self.assertEqual(False, isc.config.module_spec._validate_type({ 'item_type': 'real' }, "a", errors))
- self.assertEqual(['a should be a real'], errors)
-
- errors = []
- self.assertEqual(True, isc.config.module_spec._validate_type({ 'item_type': 'boolean' }, True, errors))
- self.assertEqual(False, isc.config.module_spec._validate_type({ 'item_type': 'boolean' }, "a", None))
- self.assertEqual(False, isc.config.module_spec._validate_type({ 'item_type': 'boolean' }, "a", errors))
- self.assertEqual(['a should be a boolean'], errors)
-
- errors = []
- self.assertEqual(True, isc.config.module_spec._validate_type({ 'item_type': 'string' }, "a", errors))
- self.assertEqual(False, isc.config.module_spec._validate_type({ 'item_type': 'string' }, 1, None))
- self.assertEqual(False, isc.config.module_spec._validate_type({ 'item_type': 'string' }, 1, errors))
- self.assertEqual(['1 should be a string'], errors)
-
- errors = []
- self.assertEqual(True, isc.config.module_spec._validate_type({ 'item_type': 'list' }, [ 1, 1], errors))
- self.assertEqual(False, isc.config.module_spec._validate_type({ 'item_type': 'list' }, 1, None))
- self.assertEqual(False, isc.config.module_spec._validate_type({ 'item_type': 'list' }, 1, errors))
- self.assertEqual(['1 should be a list'], errors)
-
- errors = []
- self.assertEqual(True, isc.config.module_spec._validate_type({ 'item_type': 'map' }, {"a": 1}, errors))
- self.assertEqual(False, isc.config.module_spec._validate_type({ 'item_type': 'map' }, 1, None))
- self.assertEqual(False, isc.config.module_spec._validate_type({ 'item_type': 'map' }, 1, errors))
- self.assertEqual(['1 should be a map'], errors)
-
- def test_validate_format(self):
- errors = []
- self.assertEqual(True, isc.config.module_spec._validate_format({ 'item_format': 'date-time' }, "2011-05-27T19:42:57Z", errors))
- self.assertEqual(False, isc.config.module_spec._validate_format({ 'item_format': 'date-time' }, "a", None))
- self.assertEqual(False, isc.config.module_spec._validate_format({ 'item_format': 'date-time' }, "a", errors))
- self.assertEqual(['format type of a should be date-time'], errors)
-
- errors = []
- self.assertEqual(True, isc.config.module_spec._validate_format({ 'item_format': 'date' }, "2011-05-27", errors))
- self.assertEqual(False, isc.config.module_spec._validate_format({ 'item_format': 'date' }, "a", None))
- self.assertEqual(False, isc.config.module_spec._validate_format({ 'item_format': 'date' }, "a", errors))
- self.assertEqual(['format type of a should be date'], errors)
-
- errors = []
- self.assertEqual(True, isc.config.module_spec._validate_format({ 'item_format': 'time' }, "19:42:57", errors))
- self.assertEqual(False, isc.config.module_spec._validate_format({ 'item_format': 'time' }, "a", None))
- self.assertEqual(False, isc.config.module_spec._validate_format({ 'item_format': 'time' }, "a", errors))
- self.assertEqual(['format type of a should be time'], errors)
-
- def test_validate_spec(self):
- spec = { 'item_name': "an_item",
- 'item_type': "string",
- 'item_optional': False,
- 'item_default': "asdf"
- }
- errors = []
- self.assertEqual(False, isc.config.module_spec._validate_spec(spec, True, {}, None))
- self.assertEqual(False, isc.config.module_spec._validate_spec(spec, True, {}, errors))
- self.assertEqual(['non-optional item an_item missing'], errors)
-
- def test_validate_unknown_items(self):
- spec = [{ 'item_name': "an_item",
- 'item_type': "string",
- 'item_optional': True,
- 'item_default': "asdf"
- }]
-
- errors = []
- self.assertEqual(True, isc.config.module_spec._validate_spec_list(spec, True, None, None))
- self.assertEqual(False, isc.config.module_spec._validate_spec_list(spec, True, { 'does_not_exist': 1 }, None))
- self.assertEqual(False, isc.config.module_spec._validate_spec_list(spec, True, { 'does_not_exist': 1 }, errors))
- self.assertEqual(['unknown item does_not_exist'], errors)
-
-
-
-if __name__ == '__main__':
- unittest.main()
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import isc
-
-class WouldBlockForever(Exception):
- """
- This is thrown by the FakeModuleCCSession if it would need
- to block forever for incoming message.
- """
- pass
-
-#
-# We can probably use a more general version of this
-#
-class FakeModuleCCSession:
- def __init__(self):
- self.subscriptions = {}
- # each entry is of the form [ channel, instance, message, want_answer ]
- self.message_queue = []
- self._socket = "ok we just need something not-None here atm"
- # if self.timeout is set to anything other than 0, and
- # the message_queue is empty when receive is called, throw
- # a SessionTimeout
- self._timeout = 0
- self._closed = False
-
- def group_subscribe(self, group_name, instance_name = None):
- if not group_name in self.subscriptions:
- self.subscriptions[group_name] = []
- if instance_name:
- self.subscriptions[group_name].append(instance_name)
-
- def group_unsubscribe(self, group_name, instance_name = None):
-
- # raises SessionError if the session has been already closed.
- if self._closed:
- raise isc.cc.SessionError("Session has been closed.")
-
- if group_name in self.subscriptions:
- if instance_name:
- if len(self.subscriptions[group_name]) > 1:
- del self.subscriptions[group_name][instance_name]
- else:
- del self.subscriptions[group_name]
- else:
- del self.subscriptions[group_name]
-
-
- def has_subscription(self, group_name, instance_name = None):
- if group_name in self.subscriptions:
- if instance_name:
- return instance_name in self.subscriptions[group_name]
- else:
- return True
- else:
- return False
-
- def group_sendmsg(self, msg, group, instance=None, to=None,
- want_answer=False):
- self.message_queue.append([ group, instance, msg, want_answer ])
- return 42
-
- def group_reply(self, env, msg):
- if 'group' in env:
- self.message_queue.append([ env['group'], None, msg, False])
-
- def group_recvmsg(self, nonblock=True, seq = None):
- for qm in self.message_queue:
- if qm[0] in self.subscriptions and (qm[1] == None or qm[1] in
- self.subscriptions[qm[0]]):
- self.message_queue.remove(qm)
- return qm[2], {'group': qm[0], 'from': qm[1]}
- if self._timeout == 0:
- if nonblock:
- return None, None
- else:
- raise WouldBlockForever(
- "Blocking read without timeout and no message ready")
- else:
- raise isc.cc.SessionTimeout("Timeout set but no data to "
- "return to group_recvmsg()")
-
- def get_message(self, channel, target = None):
- for qm in self.message_queue:
- if qm[0] == channel and qm[1] == target:
- self.message_queue.remove(qm)
- return qm[2]
- return None
-
- def close(self):
- # need to pass along somehow that this function has been called,
- self._socket = None
- self._closed = True
-
- def set_timeout(self, timeout):
- self._timeout = timeout
-
- def get_timeout(self):
- return self._timeout
+++ /dev/null
-python_PYTHON = __init__.py
-pythondir = $(pyexecdir)/isc/dns
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
-
+++ /dev/null
-from pydnspp import *
+++ /dev/null
-SUBDIRS = . tests
-
-AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
-AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CXXFLAGS = $(B10_CXXFLAGS)
-
-# There is a build problem with python3.4 (a new field tp_finalize) has been
-# added and now compiler complains about it not being properly initialized in
-# construtor. Since the whole python thing goes away, it's counter-productive
-# to spend any time on making this work on both python3.3 and 3.4, so
-# ingoring the warning seems the way to go.
-AM_CXXFLAGS += -Wno-error
-
-pythondir = $(pyexecdir)/isc
-python_LTLIBRARIES = log.la
-log_la_SOURCES = log.cc
-
-log_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
-# Note: PYTHON_CXXFLAGS may have some -Wno... workaround, which must be
-# placed after -Wextra defined in AM_CXXFLAGS
-log_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
-log_la_LDFLAGS = $(PYTHON_LDFLAGS)
-log_la_LDFLAGS += -module -avoid-version
-log_la_LIBADD = $(top_builddir)/src/lib/log/libkea-log.la
-log_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
-log_la_LIBADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
-log_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
-log_la_LIBADD += $(PYTHON_LIB)
-
-# This is not installed, it helps locate the module during tests
-EXTRA_DIST = __init__.py
-
-pytest:
- $(SHELL) tests/log_test
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-# This file is not installed. The log.so is installed into the right place.
-# It is only to find it in the .libs directory when we run as a test or
-# from the build directory.
-# But as nobody gives us the builddir explicitly (and we can't use generation
-# from .in file, as it would put us into the builddir and we wouldn't be found)
-# we guess from current directory. Any idea for something better? This should
-# be enough for the tests, but would it work for B10_FROM_SOURCE as well?
-# Should we look there? Or define something in bind10_config?
-
-import os
-import sys
-
-for base in sys.path[:]:
- loglibdir = os.path.join(base, 'isc/log/.libs')
- if os.path.exists(loglibdir):
- sys.path.insert(0, loglibdir)
-
-from log import *
+++ /dev/null
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#define PY_SSIZE_T_CLEAN
-#include <Python.h>
-#include <structmember.h>
-
-#include <config.h>
-
-#include <log/message_dictionary.h>
-#include <log/logger_manager.h>
-#include <log/logger_support.h>
-#include <log/logger.h>
-
-#include <config/ccsession.h>
-
-#include <string>
-#include <boost/bind.hpp>
-
-#include <util/python/pycppwrapper_util.h>
-#include <log/log_dbglevels.h>
-
-using namespace isc::log;
-using namespace isc::util::python;
-using std::string;
-using boost::bind;
-
-// We encountered a strange problem with Clang (clang version 2.8
-// (tags/RELEASE_28 115909)) on OSX, where unwinding the stack
-// segfaults the moment this exception was thrown and caught.
-//
-// Placing it in a named namespace instead of the original
-// unnamed namespace appears to solve this, so as a temporary
-// workaround, we create a local randomly named namespace here
-// to solve this issue.
-namespace clang_unnamed_namespace_workaround {
- // To propagate python exceptions through our code
- // This exception is used to signal to the calling function that a
- // proper Python Exception has already been set, and the caller
- // should now return NULL.
- // Since it is only used internally, and should not pass any
- // information itself, is is not derived from std::exception
- class InternalError : public std::exception {};
-}
-using namespace clang_unnamed_namespace_workaround;
-
-namespace {
-
-// This is for testing only. The real module will have it always set as
-// NULL and will use the global dictionary.
-MessageDictionary* testDictionary = NULL;
-
-PyObject*
-setTestDictionary(PyObject*, PyObject* args) {
- PyObject* enableO;
- // The API doesn't seem to provide conversion to bool,
- // so we do it little bit manually
- if (!PyArg_ParseTuple(args, "O", &enableO)) {
- return (NULL);
- }
- int enableI(PyObject_IsTrue(enableO));
- if (enableI == -1) {
- return (NULL);
- }
- bool enable(enableI != 0);
-
- try {
- delete testDictionary;
- testDictionary = NULL;
- if (enable) {
- testDictionary = new MessageDictionary;
- }
- }
- catch (const std::exception& e) {
- PyErr_SetString(PyExc_RuntimeError, e.what());
- return (NULL);
- }
- catch (...) {
- PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
- return (NULL);
- }
- Py_RETURN_NONE;
-}
-
-PyObject*
-createMessage(PyObject*, PyObject* args) {
- const char* mid;
- const char* text;
- // We parse the strings
- if (!PyArg_ParseTuple(args, "ss", &mid, &text)) {
- return (NULL);
- }
- PyObject* origMid;
- // And extract the original representation of the message
- // ID, so we can return it instead of creating another instance.
- // This call shouldn't fail if the previous suceeded.
- if (!PyArg_ParseTuple(args, "Os", &origMid, &text)) {
- return (NULL);
- }
-
- try {
- MessageDictionary* dict = testDictionary ? testDictionary :
- &MessageDictionary::globalDictionary();
-
- // We ignore the result, they will be in some kind of dupe list
- // if there's a problem
- dict->add(mid, text);
- }
- catch (const std::exception& e) {
- PyErr_SetString(PyExc_RuntimeError, e.what());
- return (NULL);
- }
- catch (...) {
- PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
- return (NULL);
- }
-
- // Return the ID
- Py_INCREF(origMid);
- return (origMid);
-}
-
-PyObject*
-getMessage(PyObject*, PyObject* args) {
- const char* mid;
- if (!PyArg_ParseTuple(args, "s", &mid)) {
- return (NULL);
- }
-
- try {
- MessageDictionary* dict = testDictionary ? testDictionary :
- &MessageDictionary::globalDictionary();
-
- const std::string& result(dict->getText(mid));
- if (result.empty()) {
- Py_RETURN_NONE;
- } else {
- return (Py_BuildValue("s", result.c_str()));
- }
- }
- catch (const std::exception& e) {
- PyErr_SetString(PyExc_RuntimeError, e.what());
- return (NULL);
- }
- catch (...) {
- PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
- return (NULL);
- }
-}
-
-PyObject*
-reset(PyObject*, PyObject*) {
- LoggerManager::reset();
- Py_RETURN_NONE;
-}
-
-PyObject*
-init(PyObject*, PyObject* args, PyObject* arg_keywords) {
- const char* root;
- const char* file(NULL);
- const char* severity("INFO");
- bool buffer = false;
- int dbglevel(0);
- const char* const keywords[] = { "name", "severity", "debuglevel", "file",
- "buffer", NULL };
- if (!PyArg_ParseTupleAndKeywords(args, arg_keywords, "s|sizb",
- const_cast<char**>(keywords), &root,
- &severity, &dbglevel, &file, &buffer)) {
- return (NULL);
- }
-
- try {
- LoggerManager::init(root, getSeverity(severity), dbglevel, file,
- buffer);
- }
- catch (const std::exception& e) {
- PyErr_SetString(PyExc_RuntimeError, e.what());
- return (NULL);
- }
- catch (...) {
- PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
- return (NULL);
- }
- Py_RETURN_NONE;
-}
-
-// This initialization is for unit tests. It allows message settings to
-// be determined by a set of B10_xxx environment variables. (See the
-// description of initLogger() for more details.) The function has been named
-// resetUnitTestRootLogger() here as being more descriptive and
-// trying to avoid confusion.
-PyObject*
-resetUnitTestRootLogger(PyObject*, PyObject*) {
- try {
- isc::log::resetUnitTestRootLogger();
- }
- catch (const std::exception& e) {
- PyErr_SetString(PyExc_RuntimeError, e.what());
- return (NULL);
- }
- catch (...) {
- PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
- return (NULL);
- }
- Py_RETURN_NONE;
-}
-
-PyObject*
-logConfigUpdate(PyObject*, PyObject* args) {
- // we have no wrappers for ElementPtr and ConfigData,
- // So we expect JSON strings and convert them.
- // The new_config object is assumed to have been validated.
-
- const char* new_config_json;
- const char* mod_spec_json;
- if (!PyArg_ParseTuple(args, "ss",
- &new_config_json, &mod_spec_json)) {
- return (NULL);
- }
-
- try {
- isc::data::ConstElementPtr new_config =
- isc::data::Element::fromJSON(new_config_json);
- isc::data::ConstElementPtr mod_spec_e =
- isc::data::Element::fromJSON(mod_spec_json);
- isc::config::ModuleSpec mod_spec(mod_spec_e);
- isc::config::ConfigData config_data(mod_spec);
- isc::config::default_logconfig_handler("logging", new_config,
- config_data);
-
- Py_RETURN_NONE;
- } catch (const isc::data::JSONError& je) {
- std::string error_msg = std::string("JSON format error: ") + je.what();
- PyErr_SetString(PyExc_TypeError, error_msg.c_str());
- } catch (const isc::data::TypeError&) {
- PyErr_SetString(PyExc_TypeError, "argument 1 of log_config_update "
- "is not a map of config data");
- } catch (const isc::config::ModuleSpecError&) {
- PyErr_SetString(PyExc_TypeError, "argument 2 of log_config_update "
- "is not a correct module specification");
- } catch (const std::exception& e) {
- PyErr_SetString(PyExc_RuntimeError, e.what());
- } catch (...) {
- PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
- }
- return (NULL);
-}
-
-PyMethodDef methods[] = {
- {"set_test_dictionary", setTestDictionary, METH_VARARGS,
- "Set or unset testing mode for message dictionary. In testing, "
- "the create_message and get_message functions work on different "
- "than the logger-global dictionary, not polluting it."},
- {"create_message", createMessage, METH_VARARGS,
- "Creates a new message in the dictionary. You shouldn't need to "
- "call this directly, it should be called by the generated message "
- "file. Returns the identifier to be used in logging. The text "
- "shouldn't be empty."},
- {"get_message", getMessage, METH_VARARGS,
- "Get a message. This function is for testing purposes and you don't "
- "need to call it. It returns None if the message does not exist."},
- {"reset", reset, METH_NOARGS,
- "Reset all logging. For testing purposes only, do not use."},
- {"init", reinterpret_cast<PyCFunction>(init), METH_VARARGS | METH_KEYWORDS,
- "Run-time initialization. You need to call this before you do any "
- "logging, to configure the root logger name. You may also provide "
- "Arguments:\n"
- "name: root logger name\n"
- "severity (optional): one of 'DEBUG', 'INFO', 'WARN', 'ERROR' or "
- "'FATAL'\n"
- "debuglevel (optional): a debug level (integer in the range 0-99) "
- "file (optional): a file name of a dictionary with message text "
- "translations\n"
- "buffer (optional), boolean, when True, causes all log messages "
- "to be stored internally until log_config_update is called, at "
- "which point they shall be logged."},
- {"resetUnitTestRootLogger", resetUnitTestRootLogger, METH_VARARGS,
- "Resets the configuration of the root logger to that set by the "
- "B10_XXX environment variables. It is aimed at unit tests, where "
- "the logging is initialized by the code under test; called before "
- "the unit test starts, this function resets the logging configuration "
- "to that in use for the C++ unit tests."},
- {"log_config_update", logConfigUpdate, METH_VARARGS,
- "Update logger settings. This method is automatically used when "
- "ModuleCCSession is initialized with handle_logging_config set "
- "to True. When called, the first argument is the new logging "
- "configuration (in JSON format). The second argument is "
- "the raw specification (as returned from "
- "ConfigData.get_module_spec().get_full_spec(), and converted to "
- "JSON format).\n"
- "Raises a TypeError if either argument is not a (correct) JSON "
- "string, or if the spec is not a correct spec.\n"
- "If this call succeeds, the global logger settings have "
- "been updated."
- },
- {NULL, NULL, 0, NULL}
-};
-
-class LoggerWrapper : public PyObject {
-// Everything is public here, as it is accessible only inside this .cc file.
-public:
- Logger *logger_;
-};
-
-extern PyTypeObject logger_type;
-
-int
-Logger_init(PyObject* po_self, PyObject* args, PyObject*) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- const char* name;
- if (!PyArg_ParseTuple(args, "s", &name)) {
- return (-1);
- }
- try {
- self->logger_ = new Logger(name);
- return (0);
- }
- catch (const std::exception& e) {
- PyErr_SetString(PyExc_RuntimeError, e.what());
- return (-1);
- }
- catch (...) {
- PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
- return (-1);
- }
-}
-
-void
-//Logger_destroy(LoggerWrapper* const self) {
-Logger_destroy(PyObject* po_self) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- delete self->logger_;
- self->logger_ = NULL;
- Py_TYPE(self)->tp_free(self);
-}
-
-// The isc::log doesn't contain function to convert this way
-const char*
-severityToText(const Severity& severity) {
- switch (severity) {
- case DEFAULT:
- return ("DEFAULT");
- case DEBUG:
- return ("DEBUG");
- case INFO:
- return ("INFO");
- case WARN:
- return ("WARN");
- case ERROR:
- return ("ERROR");
- case FATAL:
- return ("FATAL");
- default:
- return (NULL);
- }
-}
-
-PyObject*
-Logger_getEffectiveSeverity(PyObject* po_self, PyObject*) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- try {
- return (Py_BuildValue("s",
- severityToText(
- self->logger_->getEffectiveSeverity())));
- }
- catch (const std::exception& e) {
- PyErr_SetString(PyExc_RuntimeError, e.what());
- return (NULL);
- }
- catch (...) {
- PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
- return (NULL);
- }
-}
-
-PyObject*
-Logger_getEffectiveDebugLevel(PyObject* po_self, PyObject*) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- try {
- return (Py_BuildValue("i", self->logger_->getEffectiveDebugLevel()));
- }
- catch (const std::exception& e) {
- PyErr_SetString(PyExc_RuntimeError, e.what());
- return (NULL);
- }
- catch (...) {
- PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
- return (NULL);
- }
-}
-
-PyObject*
-Logger_setSeverity(PyObject* po_self, PyObject* args) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- const char* severity;
- int dbgLevel = 0;
- if (!PyArg_ParseTuple(args, "z|i", &severity, &dbgLevel)) {
- return (NULL);
- }
- try {
- self->logger_->setSeverity((severity == NULL) ? DEFAULT :
- getSeverity(severity), dbgLevel);
- }
- catch (const std::exception& e) {
- PyErr_SetString(PyExc_RuntimeError, e.what());
- return (NULL);
- }
- catch (...) {
- PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
- return (NULL);
- }
- Py_RETURN_NONE;
-}
-
-template<class FPtr> // Who should remember the pointer-to-method syntax
-PyObject*
-Logger_isLevelEnabled(LoggerWrapper* self, FPtr function) {
- try {
- if ((self->logger_->*function)()) {
- Py_RETURN_TRUE;
- } else {
- Py_RETURN_FALSE;
- }
- }
- catch (const std::exception& e) {
- PyErr_SetString(PyExc_RuntimeError, e.what());
- return (NULL);
- }
- catch (...) {
- PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
- return (NULL);
- }
-}
-
-PyObject*
-Logger_isInfoEnabled(PyObject* po_self, PyObject*) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- return (Logger_isLevelEnabled(self, &Logger::isInfoEnabled));
-}
-
-PyObject*
-Logger_isWarnEnabled(PyObject* po_self, PyObject*) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- return (Logger_isLevelEnabled(self, &Logger::isWarnEnabled));
-}
-
-PyObject*
-Logger_isErrorEnabled(PyObject* po_self, PyObject*) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- return (Logger_isLevelEnabled(self, &Logger::isErrorEnabled));
-}
-
-PyObject*
-Logger_isFatalEnabled(PyObject* po_self, PyObject*) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- return (Logger_isLevelEnabled(self, &Logger::isFatalEnabled));
-}
-
-PyObject*
-Logger_isDebugEnabled(PyObject* po_self, PyObject* args) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- int level = MIN_DEBUG_LEVEL;
- if (!PyArg_ParseTuple(args, "|i", &level)) {
- return (NULL);
- }
-
- try {
- if (self->logger_->isDebugEnabled(level)) {
- Py_RETURN_TRUE;
- } else {
- Py_RETURN_FALSE;
- }
- }
- catch (const std::exception& e) {
- PyErr_SetString(PyExc_RuntimeError, e.what());
- return (NULL);
- }
- catch (...) {
- PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
- return (NULL);
- }
-}
-
-string
-objectToStr(PyObject* object, bool convert) {
- PyObjectContainer objstr_container;
- if (convert) {
- PyObject* text_obj = PyObject_Str(object);
- if (text_obj == NULL) {
- // PyObject_Str could fail for various reasons, including because
- // the object cannot be converted to a string. We exit with
- // InternalError to preserve the PyErr set in PyObject_Str.
- throw InternalError();
- }
- objstr_container.reset(text_obj);
- object = objstr_container.get();
- }
-
- PyObjectContainer tuple_container(Py_BuildValue("(O)", object));
- const char* value;
- if (!PyArg_ParseTuple(tuple_container.get(), "s", &value)) {
- throw InternalError();
- }
- return (string(value));
-}
-
-// Generic function to output the logging message. Called by the real functions.
-template <class Function>
-PyObject*
-Logger_performOutput(Function function, PyObject* args, bool dbgLevel) {
- try {
- const Py_ssize_t number(PyObject_Length(args));
- if (number < 0) {
- return (NULL);
- }
-
- // Which argument is the first to format?
- const size_t start = dbgLevel ? 2 : 1;
- if (number < start) {
- return (PyErr_Format(PyExc_TypeError, "Too few arguments to "
- "logging call, at least %zu needed and %zd "
- "given", start, number));
- }
-
- // Extract the fixed arguments
- long dbg(0);
- if (dbgLevel) {
- PyObjectContainer dbg_container(PySequence_GetItem(args, 0));
- dbg = PyLong_AsLong(dbg_container.get());
- if (PyErr_Occurred()) {
- return (NULL);
- }
- }
-
- // We create the logging message right now. If we fail to convert a
- // parameter to string, at least the part that we already did will
- // be output
- PyObjectContainer msgid_container(PySequence_GetItem(args, start - 1));
- const string mid(objectToStr(msgid_container.get(), false));
- Logger::Formatter formatter(function(dbg, mid.c_str()));
-
- // Now process the rest of parameters, convert each to string and put
- // into the formatter. It will print itself in the end.
- for (size_t i(start); i < number; ++ i) {
- PyObjectContainer param_container(PySequence_GetItem(args, i));
- try {
- formatter = formatter.arg(objectToStr(param_container.get(),
- true));
- }
- catch (...) {
- formatter.deactivate();
- throw;
- }
- }
- Py_RETURN_NONE;
- }
- catch (const InternalError&) {
- return (NULL);
- }
- catch (const std::exception& e) {
- PyErr_SetString(PyExc_RuntimeError, e.what());
- return (NULL);
- }
- catch (...) {
- PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
- return (NULL);
- }
-}
-
-// Now map the functions into the performOutput. I wish C++ could do
-// functional programming.
-PyObject*
-Logger_debug(PyObject* po_self, PyObject* args) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- return (Logger_performOutput(bind(&Logger::debug, self->logger_, _1, _2),
- args, true));
-}
-
-PyObject*
-Logger_info(PyObject* po_self, PyObject* args) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- return (Logger_performOutput(bind(&Logger::info, self->logger_, _2),
- args, false));
-}
-
-PyObject*
-Logger_warn(PyObject* po_self, PyObject* args) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- return (Logger_performOutput(bind(&Logger::warn, self->logger_, _2),
- args, false));
-}
-
-PyObject*
-Logger_error(PyObject* po_self, PyObject* args) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- return (Logger_performOutput(bind(&Logger::error, self->logger_, _2),
- args, false));
-}
-
-PyObject*
-Logger_fatal(PyObject* po_self, PyObject* args) {
- LoggerWrapper* self = static_cast<LoggerWrapper*>(po_self);
- return (Logger_performOutput(bind(&Logger::fatal, self->logger_, _2),
- args, false));
-}
-
-PyMethodDef loggerMethods[] = {
- { "get_effective_severity", Logger_getEffectiveSeverity, METH_NOARGS,
- "Returns the effective logging severity as string" },
- { "get_effective_debug_level", Logger_getEffectiveDebugLevel, METH_NOARGS,
- "Returns the current debug level." },
- { "set_severity", Logger_setSeverity, METH_VARARGS,
- "Sets the severity of a logger. The parameters are severity as a "
- "string and, optionally, a debug level (integer in range 0-99). "
- "The severity may be NULL, in which case an inherited value is taken."
- },
- { "is_debug_enabled", Logger_isDebugEnabled, METH_VARARGS,
- "Returns if the logger would log debug message now. "
- "You can provide a desired debug level." },
- { "is_info_enabled", Logger_isInfoEnabled, METH_NOARGS,
- "Returns if the logger would log info message now." },
- { "is_warn_enabled", Logger_isWarnEnabled, METH_NOARGS,
- "Returns if the logger would log warn message now." },
- { "is_error_enabled", Logger_isErrorEnabled, METH_NOARGS,
- "Returns if the logger would log error message now." },
- { "is_fatal_enabled", Logger_isFatalEnabled, METH_NOARGS,
- "Returns if the logger would log fatal message now." },
- { "debug", Logger_debug, METH_VARARGS,
- "Logs a debug-severity message. It takes the debug level, message ID "
- "and any number of stringifiable arguments to the message." },
- { "info", Logger_info, METH_VARARGS,
- "Logs a info-severity message. It taskes the message ID and any "
- "number of stringifiable arguments to the message." },
- { "warn", Logger_warn, METH_VARARGS,
- "Logs a warn-severity message. It taskes the message ID and any "
- "number of stringifiable arguments to the message." },
- { "error", Logger_error, METH_VARARGS,
- "Logs a error-severity message. It taskes the message ID and any "
- "number of stringifiable arguments to the message." },
- { "fatal", Logger_fatal, METH_VARARGS,
- "Logs a fatal-severity message. It taskes the message ID and any "
- "number of stringifiable arguments to the message." },
- { NULL, NULL, 0, NULL }
-};
-
-PyTypeObject logger_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "isc.log.Logger",
- sizeof(LoggerWrapper), // tp_basicsize
- 0, // tp_itemsize
- Logger_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- NULL, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "Wrapper around the C++ isc::log::Logger class."
- "It is not complete, but everything important should be here.",
- NULL, // tp_traverse
- NULL, // tp_clear
- NULL, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- loggerMethods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- Logger_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-PyModuleDef iscLog = {
- { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
- "log",
- "Python bindings for the classes in the isc::log namespace.\n\n"
- "These bindings are close match to the C++ API, but they are not complete "
- "(some parts are not needed) and some are done in more python-like ways.",
- -1,
- methods,
- NULL,
- NULL,
- NULL,
- NULL
-};
-
-} // end anonymous namespace
-
-PyMODINIT_FUNC
-PyInit_log(void) {
- PyObject* mod = PyModule_Create(&iscLog);
- if (mod == NULL) {
- return (NULL);
- }
-
- // Finalize logger class and add in the definitions of the standard debug
- // levels. These can then be referred to in Python through the constants
- // log.DBGLVL_XXX.
- // N.B. These should be kept in sync with the constants defined in
- // log_dbglevels.h.
- try {
- if (PyType_Ready(&logger_type) < 0) {
- throw InternalError();
- }
- void* p = &logger_type;
- if (PyModule_AddObject(mod, "Logger",
- static_cast<PyObject*>(p)) < 0) {
- throw InternalError();
- }
-
- installClassVariable(logger_type, "DBGLVL_START_SHUT",
- Py_BuildValue("I", DBGLVL_START_SHUT));
- installClassVariable(logger_type, "DBGLVL_COMMAND",
- Py_BuildValue("I", DBGLVL_COMMAND));
- installClassVariable(logger_type, "DBGLVL_COMMAND_DATA",
- Py_BuildValue("I", DBGLVL_COMMAND_DATA));
- installClassVariable(logger_type, "DBGLVL_TRACE_BASIC",
- Py_BuildValue("I", DBGLVL_TRACE_BASIC));
- installClassVariable(logger_type, "DBGLVL_TRACE_BASIC_DATA",
- Py_BuildValue("I", DBGLVL_TRACE_BASIC_DATA));
- installClassVariable(logger_type, "DBGLVL_TRACE_DETAIL",
- Py_BuildValue("I", DBGLVL_TRACE_DETAIL));
- installClassVariable(logger_type, "DBGLVL_TRACE_DETAIL_DATA",
- Py_BuildValue("I", DBGLVL_TRACE_DETAIL_DATA));
- } catch (const InternalError&) {
- Py_DECREF(mod);
- return (NULL);
- } catch (const std::exception& ex) {
- const std::string ex_what =
- "Unexpected failure in Log initialization: " +
- std::string(ex.what());
- PyErr_SetString(PyExc_SystemError, ex_what.c_str());
- Py_DECREF(mod);
- return (NULL);
- } catch (...) {
- PyErr_SetString(PyExc_SystemError,
- "Unexpected failure in Log initialization");
- Py_DECREF(mod);
- return (NULL);
- }
-
- Py_INCREF(&logger_type);
- return (mod);
-}
+++ /dev/null
-/log_console.py
+++ /dev/null
-PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS_GEN = log_console.py
-PYTESTS_NOGEN =
-noinst_SCRIPTS = $(PYTESTS_GEN)
-EXTRA_DIST = console.out check_output.sh $(PYTESTS_NOGEN)
-
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
-LIBRARY_PATH_PLACEHOLDER =
-if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
-endif
-
-# test using command-line arguments, so use check-local target instead of TESTS
-# We need to run the cycle twice, because once the files are in builddir, once in srcdir
-check-local:
- chmod +x $(abs_builddir)/log_console.py
- $(LIBRARY_PATH_PLACEHOLDER) \
- PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/log \
- B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
- $(abs_srcdir)/check_output.sh $(abs_builddir)/log_console.py $(abs_srcdir)/console.out
-if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
- rm -f .coverage
- ${LN_S} $(abs_top_srcdir)/.coverage .coverage
-endif
- for pytest in $(PYTESTS_NOGEN) ; do \
- echo Running test: $$pytest ; \
- $(LIBRARY_PATH_PLACEHOLDER) \
- PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/log:$(abs_top_builddir)/src/lib/log/python/.libs \
- B10_TEST_PLUGIN_DIR=$(abs_top_srcdir)/src/bin/cfgmgr/plugins \
- B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
- $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
- done ; \
- for pytest in $(PYTESTS_GEN) ; do \
- echo Running test: $$pytest ; \
- chmod +x $(abs_builddir)/$$pytest ; \
- $(LIBRARY_PATH_PLACEHOLDER) \
- PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/log:$(abs_top_builddir)/src/lib/log/python/.libs \
- B10_TEST_PLUGIN_DIR=$(abs_top_srcdir)/src/bin/cfgmgr/plugins \
- B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
- $(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
- done
+++ /dev/null
-#!/bin/sh
-
-"$1" 2>&1 | sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | cut -d\ -f3- | diff - "$2" 1>&2
+++ /dev/null
-INFO [test.output] MSG_ID Message with list [1, 2, 3, 4]
-WARN [test.output] DIFFERENT Different message
-FATAL [test.output] MSG_ID Message with 2 1
-DEBUG [test.output] MSG_ID Message with 3 2
+++ /dev/null
-#!@PYTHON@
-
-import isc.log
-# This would come from a dictionary in real life
-MSG_ID = isc.log.create_message("MSG_ID", "Message with %2 %1")
-DIFFERENT = isc.log.create_message("DIFFERENT", "Different message")
-isc.log.init("test")
-logger = isc.log.Logger("output")
-
-logger.debug(20, MSG_ID, "test", "no output")
-logger.info(MSG_ID, [1, 2, 3, 4], "list")
-logger.warn(DIFFERENT)
-logger.fatal(MSG_ID, 1, 2)
-logger.set_severity("DEBUG", 99)
-logger.debug(1, MSG_ID, 2, 3)
+++ /dev/null
-SUBDIRS = work
-
-EXTRA_DIST = __init__.py
-EXTRA_DIST += init_messages.py
-EXTRA_DIST += cmdctl_messages.py
-EXTRA_DIST += ddns_messages.py
-EXTRA_DIST += libmemmgr_messages.py
-EXTRA_DIST += memmgr_messages.py
-EXTRA_DIST += stats_messages.py
-EXTRA_DIST += stats_httpd_messages.py
-EXTRA_DIST += xfrin_messages.py
-EXTRA_DIST += xfrout_messages.py
-EXTRA_DIST += zonemgr_messages.py
-EXTRA_DIST += cfgmgr_messages.py
-EXTRA_DIST += config_messages.py
-EXTRA_DIST += notify_out_messages.py
-EXTRA_DIST += libddns_messages.py
-EXTRA_DIST += libxfrin_messages.py
-EXTRA_DIST += loadzone_messages.py
-EXTRA_DIST += server_common_messages.py
-EXTRA_DIST += dbutil_messages.py
-EXTRA_DIST += msgq_messages.py
-EXTRA_DIST += pycc_messages.py
-EXTRA_DIST += util_messages.py
-
-CLEANFILES = __init__.pyc
-CLEANFILES += init_messages.pyc
-CLEANFILES += cmdctl_messages.pyc
-CLEANFILES += ddns_messages.pyc
-CLEANFILES += libmemmgr_messages.pyc
-CLEANFILES += memmgr_messages.pyc
-CLEANFILES += stats_messages.pyc
-CLEANFILES += stats_httpd_messages.pyc
-CLEANFILES += xfrin_messages.pyc
-CLEANFILES += xfrout_messages.pyc
-CLEANFILES += zonemgr_messages.pyc
-CLEANFILES += cfgmgr_messages.pyc
-CLEANFILES += config_messages.pyc
-CLEANFILES += notify_out_messages.pyc
-CLEANFILES += libddns_messages.pyc
-CLEANFILES += libxfrin_messages.pyc
-CLEANFILES += loadzone_messages.pyc
-CLEANFILES += server_common_messages.pyc
-CLEANFILES += dbutil_messages.pyc
-CLEANFILES += msgq_messages.pyc
-CLEANFILES += pycc_messages.pyc
-CLEANFILES += util_messages.pyc
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-This is a placeholder package for logging messages of various modules
-in the form of python scripts. This package is expected to be installed
-somewhere like <top-install-dir>/python3.x/site-packages/isc/log_messages
-and each message script is expected to be imported as
-"isc.log_messages.some_module_messages".
-
-We also need to allow in-source test code to get access to the message
-scripts in the same manner. That's why the package is stored in the
-directory that shares the same trailing part as the install directory,
-i.e., isc/log_messages.
-
-Furthermore, we need to support a build mode using a separate build
-tree (such as in the case with 'make distcheck'). In that case if an
-application (via a test script) imports "isc.log_messages.xxx", it
-would try to import the module under the source tree, where the
-generated message script doesn't exist. So, in the source directory
-(i.e., here) we provide dummy scripts that subsequently import the
-same name of module under the "work" sub-package. The caller
-application is assumed to have <top_builddir>/src/lib/python/isc/log_messages
-in its module search path (this is done by including
-$(COMMON_PYTHON_PATH) in the PYTHONPATH environment variable),
-which ensures the right directory is chosen.
-
-A python module or program that defines its own log messages needs to
-make sure that the setup described above is implemented. It's a
-complicated process, but can generally be done by following a common
-pattern:
-
-1. Create the dummy script (see above) for the module and update
- Makefile.am in this directory accordingly. See (and use)
- a helper shell script named gen-forwarder.sh.
-2. Update Makefil.am of the module that defines the log message. The
- following are a sample snippet for Makefile.am for a module named
- "mymodule" (which is supposed to be generated from a file
- "mymodule_messages.mes"). In many cases it should work simply by
- replacing 'mymodule' with the actual module name.
-
-==================== begin Makefile.am additions ===================
-nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/mymodule_messages.py
-pylogmessagedir = $(pyexecdir)/isc/log_messages/
-
-CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/mymodule_messages.py
-CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/mymodule_messages.pyc
-
-EXTRA_DIST = mymodule_messages.mes
-
-$(PYTHON_LOGMSGPKG_DIR)/work/mymodule_messages.py : mymodule_messages.mes
- $(top_builddir)/src/lib/log/compiler/message \
- -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/mymodule_messages.mes
-
-# This rule ensures mymodule_messages.py is (re)generated as a result of
-# 'make'. If there's no other appropriate target, specify
-# mymodule_messages.py in BUILT_SOURCES.
-mymodule: <other source files> $(PYTHON_LOGMSGPKG_DIR)/work/mymodule_messages.py
-===================== end Makefile.am additions ====================
-
-Notes:
-- "nodist_" prefix is important. Without this, 'make distcheck' tries
- to make _messages.py before actually starting the main build, which
- would fail because the message compiler isn't built yet.
-- "pylogmessage" is a prefix for python scripts that define log
- messages and are expected to be installed in the common isc/log_messages
- directory. It's intentionally named differently from the common
- "python" prefix (as in python_PYTHON), because the latter may be
- used for other scripts in the same Makefile.am file.
-- $(PYTHON_LOGMSGPKG_DIR) should be set to point to this directory (or
- the corresponding build directory if it's different) by the
- configure script.
+++ /dev/null
-"""
-This is an in-source forwarder package redirecting to work/* scripts.
-"""
+++ /dev/null
-from work.cfgmgr_messages import *
+++ /dev/null
-from work.cmdctl_messages import *
+++ /dev/null
-from work.config_messages import *
+++ /dev/null
-from work.dbutil_messages import *
+++ /dev/null
-from work.ddns_messages import *
+++ /dev/null
-#!/bin/sh
-
-# Copyright (C) 2011 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-MODULE_NAME=$1
-if test -z $MODULE_NAME; then
- echo 'Usage: gen-forwarder.sh module_name'
- exit 1
-fi
-
-echo "from work.${MODULE_NAME}_messages import *" > ${MODULE_NAME}_messages.py
-echo "Forwarder python script is generated. Make sure to perform:"
-echo "git add ${MODULE_NAME}_messages.py"
-echo "and add the following to Makefile.am:"
-echo "EXTRA_DIST += ${MODULE_NAME}_messages.py"
-echo "CLEANFILES += ${MODULE_NAME}_messages.pyc"
+++ /dev/null
-from work.init_messages import *
+++ /dev/null
-from work.libddns_messages import *
+++ /dev/null
-from work.libmemmgr_messages import *
+++ /dev/null
-from work.libxfrin_messages import *
+++ /dev/null
-from work.loadzone_messages import *
+++ /dev/null
-from work.memmgr_messages import *
+++ /dev/null
-from work.msgq_messages import *
+++ /dev/null
-from work.notify_out_messages import *
+++ /dev/null
-from work.pycc_messages import *
+++ /dev/null
-from work.server_common_messages import *
+++ /dev/null
-from work.stats_httpd_messages import *
+++ /dev/null
-from work.stats_messages import *
+++ /dev/null
-from work.util_messages import *
+++ /dev/null
-/__init__.py
-/*_messages.py
+++ /dev/null
-# .py is generated in the builddir by the configure script so that test
-# scripts can refer to it when a separate builddir is used.
-
-python_PYTHON = __init__.py
-
-pythondir = $(pyexecdir)/isc/log_messages/
-
-CLEANFILES = __init__.pyc __init__.pyo
-CLEANDIRS = __pycache__
-
-EXTRA_DIST = README
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-The __init__.py.in in this directory is meant to be processed by
-configure so that the generated __init__.py ends up in the builddir, and
-not the srcdir. This is because Python associates a module with a
-directory, and you can't have portions of the module in two separate
-directories.
+++ /dev/null
-"""
-This package is a placeholder for python scripts of log messages.
-"""
+++ /dev/null
-from work.xfrin_messages import *
+++ /dev/null
-from work.xfrout_messages import *
+++ /dev/null
-from work.zonemgr_messages import *
+++ /dev/null
-SUBDIRS = tests
-
-python_PYTHON = __init__.py addr.py parse.py
-
-pythondir = $(pyexecdir)/isc/net
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-"""
-Here are function and classes that are related to networking.
-"""
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""Module where address representations live."""
-import socket
-import re
-
-class InvalidAddress(ValueError):
- """Exception for invalid addresses."""
- pass
-
-class IPAddr:
- """Stores an IPv4 or IPv6 address."""
- family = None
- addr = None
-
- def __init__(self, addr):
- """
- Creates the address object from a string representation. It raises
- an InvalidAddr exception if the provided string isn't valid address.
- """
- try:
- addrinfo = socket.getaddrinfo(addr, None, 0, 0, 0,
- socket.AI_NUMERICHOST)[0]
- self.family = addrinfo[0]
- if not self.family in [socket.AF_INET, socket.AF_INET6]:
- raise InvalidAddress(
- 'IPAddr can hold only IPv4 or IPv6 address')
- self.addr = socket.inet_pton(self.family, addr)
- except socket.error as e:
- raise InvalidAddress(str(e))
-
- def __str__(self):
- return socket.inet_ntop(self.family, self.addr)
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-Checking and parsing of ports and IP addresses.
-"""
-
-from isc.net.addr import IPAddr, InvalidAddress
-import socket
-
-def port_parse(port):
- """
- Takes a port as an int or string and checks if it is valid. It returns
- the port as int. If it is not a valid port (the string doesn't contain
- number or it is not in the valid range), it raises ValueError.
- """
- inted = int(port)
- if inted < 0:
- raise ValueError("Port value " + str(inted) +
- " too small, allower range is 0-65535")
- if inted > 65535:
- raise ValueError("Port value " + str(inted) +
- " too large, allowed range is 0-65535")
- return inted
-
-def addr_parse(addr):
- """
- Checks and parses an IP address (either IPv4 or IPv6) and returns
- the IPAddr object. It raises ValueError if the passed string is not
- valid IP address.
- """
- try:
- return IPAddr(addr)
- except InvalidAddress:
- raise ValueError('Value ' + addr +
- ' is not valid IPv4 or IPv6 address')
+++ /dev/null
-PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = addr_test.py parse_test.py
-EXTRA_DIST = $(PYTESTS)
-
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
-LIBRARY_PATH_PLACEHOLDER =
-if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
-endif
-
-# test using command-line arguments, so use check-local target instead of TESTS
-check-local:
-if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
- rm -f .coverage
- ${LN_S} $(abs_top_srcdir)/.coverage .coverage
-endif
- for pytest in $(PYTESTS) ; do \
- echo Running test: $$pytest ; \
- $(LIBRARY_PATH_PLACEHOLDER) \
- PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
- $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
- done
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""Tests for the isc.net.addr module."""
-import unittest
-import socket
-from isc.net.addr import IPAddr, InvalidAddress
-
-class TestIPAddr(unittest.TestCase):
- """Test for the IPAddr class."""
- def test_v6ok(self):
- """Test if we can parse IPv6."""
- addr = IPAddr('2001:4f8::1')
- self.assertEqual(addr.family, socket.AF_INET6)
- self.assertEqual(addr.addr, socket.inet_pton(socket.AF_INET6, '2001:4f8::1'))
-
- def test_v4ok(self):
- """Test if we can parse IPv4."""
- addr = IPAddr('127.127.127.127')
- self.assertEqual(addr.family, socket.AF_INET)
- self.assertEqual(addr.addr, socket.inet_aton('127.127.127.127'))
-
- def test_badaddr(self):
- """Test if we raise on wrong address."""
- self.assertRaises(InvalidAddress, IPAddr, 'foobar')
- self.assertRaises(InvalidAddress, IPAddr, 'foo::bar')
- self.assertRaises(InvalidAddress, IPAddr, '123')
- self.assertRaises(InvalidAddress, IPAddr, '123.456.789.0')
- self.assertRaises(InvalidAddress, IPAddr, '127/8')
- self.assertRaises(InvalidAddress, IPAddr, '0/0')
- self.assertRaises(InvalidAddress, IPAddr, '1.2.3.4/32')
- self.assertRaises(InvalidAddress, IPAddr, '0')
- self.assertRaises(InvalidAddress, IPAddr, '')
-
-if __name__ == '__main__':
- unittest.main()
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""Tests for isc.net.parse."""
-import unittest
-import socket
-from isc.net.parse import port_parse, addr_parse
-
-class TestCheckPort(unittest.TestCase):
- """
- Testcases for the isc.net.port_parse function
- """
- def test_fail(self):
- """
- Test if it fails on invalid input in the correct way.
- """
- self.assertRaises(ValueError, port_parse, "not a number")
- self.assertRaises(ValueError, port_parse, -1)
- self.assertRaises(ValueError, port_parse, 65536)
-
- def test_success(self):
- """
- Test if it succeeds on valid inputs and returns the correct output
- """
- self.assertEqual(port_parse(0), 0)
- self.assertEqual(port_parse("65535"), 65535)
- self.assertEqual(port_parse(1234), 1234)
-
-class TestCheckIP(unittest.TestCase):
- """
- Testcases for the isc.net.ip_par function
- """
- def test_fail(self):
- """
- Test if it fails on invalid input the correct way.
- """
- self.assertRaises(ValueError, addr_parse, "not an address")
- self.assertRaises(ValueError, addr_parse, "123.456.789.012")
- self.assertRaises(ValueError, addr_parse, "123.0.0.")
- # Address range not allowed
- self.assertRaises(ValueError, addr_parse, "192.0.2.0/24")
- try:
- # XXX: MacOS X's inet_pton() doesn't reject this form, so we
- # check the behavior of the underlying library implementation
- # before the actual test
- socket.inet_pton(socket.AF_INET, "0000.0.0.0")
- except socket.error:
- self.assertRaises(ValueError, addr_parse, "0000.0.0.0")
- self.assertRaises(ValueError, addr_parse, "bada:ddr0::")
- self.assertRaises(ValueError, addr_parse, "2001:db8::/32")
- # This should be one part too long (eg. 9 segments)
- self.assertRaises(ValueError, addr_parse, "2001:db8:0:0:0:0:0:0:0")
- # Only one :: allowed
- self.assertRaises(ValueError, addr_parse, "2001::db8::c")
-
- def test_success(self):
- """
- Test if it succeeds on valid inputs and returns addresses that look
- the same.
- """
- self.assertEqual("192.0.2.0", str(addr_parse("192.0.2.0")))
- # The OS could return something else than canonical form, in which
- # case the test would fail. However, I do not see an easy way to fix
- # the test, so it is left this way unless someone finds an OS that
- # does return something else.
- self.assertEqual("2001:bd8::", str(addr_parse("2001:bd8::")))
- # It should strip the unnecesarry parts
- self.assertEqual("2001:bd8::", str(addr_parse("2001:bd8:0:0:0:0:0:0")))
- self.assertEqual("::", str(addr_parse("::")))
- self.assertEqual("2001:bd8::", str(addr_parse("2001:bd8::0.0.0.0")))
-
-if __name__ == "__main__":
- unittest.main()
+++ /dev/null
-SUBDIRS = . tests
-
-python_PYTHON = __init__.py
-python_PYTHON += sysinfo.py
-
-pythondir = $(pyexecdir)/isc/sysinfo
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-from isc.sysinfo.sysinfo import *
+++ /dev/null
-# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-'''This module returns system information.'''
-
-import os
-import sys
-import re
-import subprocess
-import os.path
-import platform
-import time
-from datetime import timedelta
-
-class SysInfo:
- def __init__(self):
- self._num_processors = None
- self._endianness = 'Unknown'
- self._hostname = ''
- self._platform_name = 'Unknown'
- self._platform_version = 'Unknown'
- self._platform_machine = 'Unknown'
- self._platform_is_smp = None
- self._uptime = None
- self._loadavg = None
- self._mem_total = None
- self._mem_free = None
- self._mem_swap_total = None
- self._mem_swap_free = None
- self._net_interfaces = 'Unknown\n'
- self._net_routing_table = 'Unknown\n'
- self._net_stats = 'Unknown\n'
- self._net_connections = 'Unknown\n'
-
- # The following are Linux specific, and should eventually be removed
- # from this level; for now we simply default to None (so they won't
- # be printed)
- self._platform_distro = None
- self._mem_cached = None
- self._mem_buffers = None
-
- def get_num_processors(self):
- """Returns the number of processors. This is the number of
- hyperthreads when hyper-threading is enabled.
- """
- return self._num_processors
-
- def get_endianness(self):
- """Returns 'big' or 'little'."""
- return self._endianness
-
- def get_platform_hostname(self):
- """Returns the hostname of the system."""
- return self._hostname
-
- def get_platform_name(self):
- """Returns the platform name (uname -s)."""
- return self._platform_name
-
- def get_platform_version(self):
- """Returns the platform version (uname -v)."""
- return self._platform_version
-
- def get_platform_machine(self):
- """Returns the platform machine architecture."""
- return self._platform_machine
-
- def get_platform_is_smp(self):
- """Returns True if an SMP kernel is being used, False otherwise."""
- return self._platform_is_smp
-
- def get_platform_distro(self):
- """Returns the name of the OS distribution in use.
-
- Note: the concept of 'distribution' is Linux specific. This shouldn't
- be at this level.
-
- """
- return self._platform_distro
-
- def get_uptime(self):
- """Returns the uptime in seconds."""
- return self._uptime
-
- def get_uptime_desc(self):
- """Returns the uptime in human readable form.
-
- The format is the result of str() method of the standard library
- datetime.timedelta class. It returns None if _uptime is None.
-
- """
- if self._uptime is None:
- return None
-
- return str(timedelta(seconds=self._uptime))
-
- def get_loadavg(self):
- """Returns the load average as 3 floating point values in an array."""
- return self._loadavg
-
- def get_mem_total(self):
- """Returns the total amount of memory in bytes."""
- return self._mem_total
-
- def get_mem_free(self):
- """Returns the amount of free memory in bytes."""
- return self._mem_free
-
- def get_mem_cached(self):
- """Returns the amount of cached memory in bytes."""
- return self._mem_cached
-
- def get_mem_buffers(self):
- """Returns the amount of buffer in bytes."""
- return self._mem_buffers
-
- def get_mem_swap_total(self):
- """Returns the total amount of swap in bytes."""
- return self._mem_swap_total
-
- def get_mem_swap_free(self):
- """Returns the amount of free swap in bytes."""
- return self._mem_swap_free
-
- def get_net_interfaces(self):
- """Returns information about network interfaces (as a multi-line string)."""
- return self._net_interfaces
-
- def get_net_routing_table(self):
- """Returns information about network routing table (as a multi-line string)."""
- return self._net_routing_table
-
- def get_net_stats(self):
- """Returns network statistics (as a multi-line string)."""
- return self._net_stats
-
- def get_net_connections(self):
- """Returns network connection information (as a multi-line string)."""
- return self._net_connections
-
-class SysInfoPOSIX(SysInfo):
- """Common POSIX implementation of the SysInfo class.
- See the SysInfo class documentation for more information.
- """
- def __init__(self):
- super().__init__()
-
- self._num_processors = os.sysconf('SC_NPROCESSORS_CONF')
- self._endianness = sys.byteorder
-
- u = os.uname()
- self._platform_name = u[0]
- self._hostname = u[1]
- self._platform_version = u[2]
- self._platform_machine = u[4]
-
- self._loadavg = os.getloadavg()
-
-class SysInfoLinux(SysInfoPOSIX):
- """Linux implementation of the SysInfo class.
- See the SysInfo class documentation for more information.
- """
- def __init__(self):
- super().__init__()
-
- with open('/proc/sys/kernel/hostname') as f:
- self._hostname = f.read().strip()
-
- with open('/proc/version') as f:
- self._platform_is_smp = ' SMP ' in f.read().strip()
-
- with open('/proc/uptime') as f:
- u = f.read().strip().split(' ')
- if len(u) > 1:
- self._uptime = int(round(float(u[0])))
-
- with open('/proc/loadavg') as f:
- l = f.read().strip().split(' ')
- if len(l) >= 3:
- self._loadavg = (float(l[0]), float(l[1]), float(l[2]))
-
- with open('/proc/meminfo') as f:
- m = f.readlines()
- for line in m:
- r = re.match('^MemTotal:\s+(.*)\s*kB', line)
- if r:
- self._mem_total = int(r.group(1).strip()) * 1024
- continue
- r = re.match('^MemFree:\s+(.*)\s*kB', line)
- if r:
- self._mem_free = int(r.group(1).strip()) * 1024
- continue
- r = re.match('^Cached:\s+(.*)\s*kB', line)
- if r:
- self._mem_cached = int(r.group(1).strip()) * 1024
- continue
- r = re.match('^Buffers:\s+(.*)\s*kB', line)
- if r:
- self._mem_buffers = int(r.group(1).strip()) * 1024
- continue
- r = re.match('^SwapTotal:\s+(.*)\s*kB', line)
- if r:
- self._mem_swap_total = int(r.group(1).strip()) * 1024
- continue
- r = re.match('^SwapFree:\s+(.*)\s*kB', line)
- if r:
- self._mem_swap_free = int(r.group(1).strip()) * 1024
- continue
-
- self._platform_distro = None
-
- try:
- s = subprocess.check_output(['lsb_release', '-a'])
- for line in s.decode('utf-8').split('\n'):
- r = re.match('^Description:(.*)', line)
- if r:
- self._platform_distro = r.group(1).strip()
- break
- except (subprocess.CalledProcessError, OSError):
- pass
-
- if self._platform_distro is None:
- files = ['/etc/debian_release',
- '/etc/debian_version',
- '/etc/SuSE-release',
- '/etc/UnitedLinux-release',
- '/etc/mandrake-release',
- '/etc/gentoo-release',
- '/etc/fedora-release',
- '/etc/redhat-release',
- '/etc/redhat_version',
- '/etc/slackware-release',
- '/etc/slackware-version',
- '/etc/arch-release',
- '/etc/lsb-release',
- '/etc/mageia-release']
- for fn in files:
- if os.path.exists(fn):
- with open(fn) as f:
- self._platform_distro = f.read().strip()
- break
-
- if self._platform_distro is None:
- self._platform_distro = 'Unknown'
-
- try:
- s = subprocess.check_output(['ip', 'addr'])
- self._net_interfaces = s.decode('utf-8')
- except (subprocess.CalledProcessError, OSError):
- self._net_interfaces = 'Warning: "ip addr" command failed.\n'
-
- try:
- s = subprocess.check_output(['ip', 'route'])
- self._net_routing_table = s.decode('utf-8')
- self._net_routing_table += '\n'
- s = subprocess.check_output(['ip', '-f', 'inet6', 'route'])
- self._net_routing_table += s.decode('utf-8')
- except (subprocess.CalledProcessError, OSError):
- self._net_routing_table = 'Warning: "ip route" or "ip -f inet6 route" command failed.\n'
-
- try:
- s = subprocess.check_output(['netstat', '-s'])
- self._net_stats = s.decode('utf-8')
- except (subprocess.CalledProcessError, OSError):
- self._net_stats = 'Warning: "netstat -s" command failed.\n'
-
- try:
- s = subprocess.check_output(['netstat', '-apn'])
- self._net_connections = s.decode('utf-8')
- except (subprocess.CalledProcessError, OSError):
- self._net_connections = 'Warning: "netstat -apn" command failed.\n'
-
-class SysInfoBSD(SysInfoPOSIX):
- """Common BSD implementation of the SysInfo class.
- See the SysInfo class documentation for more information.
- """
- def __init__(self):
- super().__init__()
-
- try:
- s = subprocess.check_output(['hostname'])
- self._hostname = s.decode('utf-8').strip()
- except (subprocess.CalledProcessError, OSError):
- pass
-
- try:
- s = subprocess.check_output(['sysctl', '-n', 'hw.physmem'])
- self._mem_total = int(s.decode('utf-8').strip())
- except (subprocess.CalledProcessError, OSError):
- pass
-
- try:
- s = subprocess.check_output(['ifconfig'])
- self._net_interfaces = s.decode('utf-8')
- except (subprocess.CalledProcessError, OSError):
- self._net_interfaces = 'Warning: "ifconfig" command failed.\n'
-
- try:
- s = subprocess.check_output(['netstat', '-s'])
- self._net_stats = s.decode('utf-8')
- except (subprocess.CalledProcessError, OSError):
- self._net_stats = 'Warning: "netstat -s" command failed.\n'
-
- try:
- s = subprocess.check_output(['netstat', '-an'])
- self._net_connections = s.decode('utf-8')
- except (subprocess.CalledProcessError, OSError):
- self._net_connections = 'Warning: "netstat -an" command failed.\n'
-
- try:
- s = subprocess.check_output(['netstat', '-nr'])
- self._net_routing_table = s.decode('utf-8')
- except (subprocess.CalledProcessError, OSError):
- self._net_connections = 'Warning: "netstat -nr" command failed.\n'
-
-class SysInfoNetBSD(SysInfoBSD):
- """NetBSD and OpenBSD implementation of the SysInfo class.
- See the SysInfo class documentation for more information.
- """
- def __init__(self):
- super().__init__()
-
- try:
- s = subprocess.check_output(['sysctl', '-n', 'kern.boottime'])
- t = s.decode('utf-8').strip()
- sec = time.time() - int(t)
- self._uptime = int(round(sec))
- except (subprocess.CalledProcessError, OSError):
- pass
-
- try:
- s = subprocess.check_output(['sysctl', '-n', 'vm.loadavg'])
- l = s.decode('utf-8').strip().split(' ')
- if len(l) >= 3:
- self._loadavg = (float(l[0]), float(l[1]), float(l[2]))
- except (subprocess.CalledProcessError, OSError):
- pass
-
- try:
- # We use the size of free-list from the vmstat result.
- s = subprocess.check_output(['vmstat'])
- lines = s.decode('utf-8').split('\n')
- v = re.split('\s+', lines[2])
- self._mem_free = int(v[5]) * 1024
- except (subprocess.CalledProcessError, OSError):
- pass
-
- try:
- s = subprocess.check_output(['swapctl', '-s', '-k'])
- l = s.decode('utf-8').strip()
- r = re.match('^total: (\d+) 1K-blocks allocated, (\d+) used, (\d+) available', l)
- if r:
- self._mem_swap_total = int(r.group(1).strip()) * 1024
- self._mem_swap_free = int(r.group(3).strip()) * 1024
- except (subprocess.CalledProcessError, OSError):
- pass
-
-class SysInfoFreeBSDOSX(SysInfoBSD):
- """Shared code for the FreeBSD and OS X implementations of the SysInfo
- class. See the SysInfo class documentation for more information.
- """
- def __init__(self):
- super().__init__()
-
- try:
- s = subprocess.check_output(['sysctl', '-n', 'kern.boottime'])
- t = s.decode('utf-8').strip()
- r = re.match('^\{\s+sec\s+\=\s+(\d+),.*', t)
- if r:
- sec = time.time() - int(r.group(1))
- self._uptime = int(round(sec))
- except (subprocess.CalledProcessError, OSError):
- pass
-
- try:
- s = subprocess.check_output(['sysctl', '-n', 'vm.loadavg'])
- l = s.decode('utf-8').strip()
- r = re.match('^\{(.*)\}$', l)
- if r:
- la = r.group(1).strip().split(' ')
- else:
- la = l.split(' ')
- if len(la) >= 3:
- self._loadavg = (float(la[0]), float(la[1]), float(la[2]))
- except (subprocess.CalledProcessError, OSError):
- pass
-
-class SysInfoFreeBSD(SysInfoFreeBSDOSX):
- """FreeBSD implementation of the SysInfo class.
- See the SysInfo class documentation for more information.
- """
- def __init__(self):
- super().__init__()
-
- try:
- # There doesn't seem to be an easy way to reliably detect whether
- # the kernel was built with SMP support on FreeBSD. We use
- # a sysctl variable that is only defined in SMP kernels.
- # This assumption seems to hold for several recent versions of
- # FreeBSD, but it may not always be so for future versions.
- s = subprocess.check_output(['sysctl', '-n',
- 'kern.smp.forward_signal_enabled'])
- self._platform_is_smp = True # the value doesn't matter
- except subprocess.CalledProcessError:
- # if this variable isn't defined we should see this exception.
- # interpret it as an indication of non-SMP kernel.
- self._platform_is_smp = False
- except OSError:
- pass
-
- try:
- # We use the size of free-list from the vmstat result.
- s = subprocess.check_output(['vmstat', '-H'])
- lines = s.decode('utf-8').split('\n')
- v = re.split('\s+', lines[2])
- self._mem_free = int(v[5]) * 1024
- except (subprocess.CalledProcessError, OSError):
- pass
-
- try:
- s = subprocess.check_output(['swapctl', '-s', '-k'])
- l = s.decode('utf-8').strip()
- r = re.match('^Total:\s+(\d+)\s+(\d+)', l)
- if r:
- self._mem_swap_total = int(r.group(1).strip()) * 1024
- self._mem_swap_free = self._mem_swap_total - (int(r.group(2).strip()) * 1024)
- except (subprocess.CalledProcessError, OSError):
- pass
-
-
-
-class SysInfoOSX(SysInfoFreeBSDOSX):
- """OS X (Darwin) implementation of the SysInfo class.
- See the SysInfo class documentation for more information.
- """
- def __init__(self):
- super().__init__()
-
- # note; this call overrides the value already set when hw.physmem
- # was read. However, on OSX, physmem is not necessarily the correct
- # value. But since it does not fail and does work on most BSD's, it's
- # left in the base class and overwritten here
- self._mem_total = None
- try:
- s = subprocess.check_output(['sysctl', '-n', 'hw.memsize'])
- self._mem_total = int(s.decode('utf-8').strip())
- except (subprocess.CalledProcessError, OSError):
- pass
-
- try:
- s = subprocess.check_output(['vm_stat'])
- lines = s.decode('utf-8').split('\n')
- # store all values in a dict
- values = {}
- page_size = None
- page_size_re = re.compile('.*page size of ([0-9]+) bytes')
- for line in lines:
- page_size_m = page_size_re.match(line)
- if page_size_m:
- page_size = int(page_size_m.group(1))
- else:
- key, _, value = line.partition(':')
- values[key] = value.strip()[:-1]
- # Only calculate memory if page size is known
- if page_size is not None:
- self._mem_free = int(values['Pages free']) * page_size +\
- int(values['Pages speculative']) * page_size
- except (subprocess.CalledProcessError, OSError):
- pass
-
- try:
- s = subprocess.check_output(['sysctl', '-n', 'vm.swapusage'])
- l = s.decode('utf-8').strip()
- r = re.match('^total = (\d+\.\d+)M\s+used = (\d+\.\d+)M\s+free = (\d+\.\d+)M', l)
- if r:
- self._mem_swap_total = float(r.group(1).strip()) * 1024
- self._mem_swap_free = float(r.group(3).strip()) * 1024
- except (subprocess.CalledProcessError, OSError):
- pass
-
-
-class SysInfoTestcase(SysInfo):
- def __init__(self):
- super().__init__()
- self._endianness = 'bigrastafarian'
- self._platform_name = 'b10test'
- self._uptime = 131072
-
-def SysInfoFromFactory():
- osname = platform.system()
- if osname == 'Linux':
- return SysInfoLinux()
- elif (osname == 'NetBSD') or (osname == 'OpenBSD'):
- return SysInfoNetBSD()
- elif osname == 'FreeBSD':
- return SysInfoFreeBSD()
- elif osname == 'Darwin':
- return SysInfoOSX()
- elif osname == 'BIND10Testcase':
- return SysInfoTestcase()
- else:
- return SysInfoPOSIX()
+++ /dev/null
-PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = sysinfo_test.py
-EXTRA_DIST = $(PYTESTS)
-
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
-if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
-endif
-
-# test using command-line arguments, so use check-local target instead of TESTS
-check-local:
-if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
- rm -f .coverage
- ${LN_S} $(abs_top_srcdir)/.coverage .coverage
-endif
- for pytest in $(PYTESTS) ; do \
- echo Running test: $$pytest ; \
- $(LIBRARY_PATH_PLACEHOLDER) \
- PYTHONPATH=$(COMMON_PYTHON_PATH) \
- $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
- done
+++ /dev/null
-# Copyright (C) 2012 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-from isc.sysinfo import *
-import os
-import unittest
-import platform
-import subprocess
-import time
-
-# different fake 'number of processors' values used for the different
-# operating systems
-NPROCESSORS_LINUX = 42
-NPROCESSORS_OPENBSD = 43
-NPROCESSORS_FREEBSD = 44
-NPROCESSORS_OSX = 45
-
-
-def _my_testcase_platform_system():
- return 'BIND10Testcase'
-
-def _my_linux_platform_system():
- return 'Linux'
-
-def _my_linux_os_sysconf(key):
- if key == 'SC_NPROCESSORS_CONF':
- return NPROCESSORS_LINUX
- assert False, 'Unhandled key'
-
-class MyLinuxFile:
- def __init__(self, filename):
- self._filename = filename
-
- def read(self):
- if self._filename == '/proc/sys/kernel/hostname':
- return 'myhostname'
- elif self._filename == '/proc/version':
- return 'An SMP version string'
- elif self._filename == '/proc/uptime':
- return '172800.75 139993.71'
- elif self._filename == '/proc/loadavg':
- return '0.1 0.2 0.3 0.4'
- else:
- assert False, 'Unhandled filename'
-
- def readlines(self):
- if self._filename == '/proc/meminfo':
- return ['MemTotal: 3083872 kB',
- 'MemFree: 870492 kB',
- 'Buffers: 27412 kB',
- 'Cached: 1303860 kB',
- 'SwapTotal: 4194300 kB',
- 'SwapFree: 3999464 kB']
- else:
- assert False, 'Unhandled filename'
-
- def close(self):
- return
-
- def __enter__(self):
- return self
-
- def __exit__(self, type, value, traceback):
- return
-
-def _my_linux_open(filename):
- return MyLinuxFile(filename)
-
-def _my_linux_subprocess_check_output(command):
- assert type(command) == list, 'command argument is not a list'
- if command == ['lsb_release', '-a']:
- return b'Description: My Distribution\n'
- elif command == ['ip', 'addr']:
- return b'qB2osV6vUOjqm3P/+tQ4d92xoYz8/U8P9v3KWRpNwlI=\n'
- elif command == ['ip', 'route']:
- return b'VGWAS92AlS14Pl2xqENJs5P2Ihe6Nv9g181Mu6Zz+aQ=\n'
- elif command == ['ip', '-f', 'inet6', 'route']:
- return b'XfizswwNA9NkXz6K36ZExpjV08Y5IXkHI8jjDSV+5Nc=\n'
- elif command == ['netstat', '-s']:
- return b'osuxbrcc1g9VgaF4yf3FrtfodrfATrbSnjhqhuQSAs8=\n'
- elif command == ['netstat', '-apn']:
- return b'Z+w0lwa02/T+5+EIio84rrst/Dtizoz/aL9Im7J7ESA=\n'
- else:
- assert False, 'Unhandled command'
-
-def _my_openbsd_platform_system():
- return 'OpenBSD'
-
-def _my_openbsd_os_sysconf(key):
- if key == 'SC_NPROCESSORS_CONF':
- return NPROCESSORS_OPENBSD
- assert False, 'Unhandled key'
-
-def _my_openbsd_platform_uname():
- return ('OpenBSD', 'test.example.com', '5.0', '', 'amd64')
-
-# For the BSD types, there is a hierarchy that mostly resembles the
-# class hierarchy in the sysinfo library;
-# These are output strings of commands that sysinfo calls
-#
-# The test hierarchy is used as follows:
-# Each operating system has its own _my_<OS>_subprocess_check_output
-# call. If the call is not found, it calls it's 'parent' (e.g.
-# for openbsd that is my_bsd_subprocesses_check_output).
-#
-# If that returns None, the call had no test value and the test fails
-# (and needs to be updated).
-# The child classes are checked first so that they can override
-# output from the parents, if necessary.
-#
-# Some parents have their own parent
-# (e.g. _my_freebsd_osx_subprocess_check_output), in that case,
-# if they do not recognize the command, they simply return whatever
-# their parent returns
-
-def _my_bsd_subprocess_check_output(command):
- '''subprocess output for all bsd types'''
- assert type(command) == list, 'command argument is not a list'
- if command == ['hostname']:
- return b'test.example.com\n'
- elif command == ['sysctl', '-n', 'hw.physmem']:
- return b'543214321\n'
- elif command == ['ifconfig']:
- return b'qB2osV6vUOjqm3P/+tQ4d92xoYz8/U8P9v3KWRpNwlI=\n'
- elif command == ['netstat', '-s']:
- return b'osuxbrcc1g9VgaF4yf3FrtfodrfATrbSnjhqhuQSAs8=\n'
- elif command == ['netstat', '-an']:
- return b'Z+w0lwa02/T+5+EIio84rrst/Dtizoz/aL9Im7J7ESA=\n'
- elif command == ['netstat', '-nr']:
- return b'XfizswwNA9NkXz6K36ZExpjV08Y5IXkHI8jjDSV+5Nc=\n'
- else:
- return None
-
-def _my_openbsd_subprocess_check_output(command):
- assert type(command) == list, 'command argument is not a list'
- if command == ['sysctl', '-n', 'kern.boottime']:
- return bytes(str(int(time.time() - 76632)), 'utf-8')
- elif command == ['sysctl', '-n', 'vm.loadavg']:
- return b'0.7 0.9 0.8\n'
- elif command == ['vmstat']:
- return b' procs memory page disks traps cpu\n r b w avm fre flt re pi po fr sr wd0 cd0 int sys cs us sy id\n 0 0 0 121212 123456 47 0 0 0 0 0 2 0 2 80 14 0 1 99\n'
- elif command == ['swapctl', '-s', '-k']:
- return b'total: 553507 1K-blocks allocated, 2 used, 553505 available'
- else:
- bsd_output = _my_bsd_subprocess_check_output(command)
- if bsd_output is not None:
- return bsd_output
- else:
- assert False, 'Unhandled command'
-
-def _my_freebsd_platform_system():
- return 'FreeBSD'
-
-def _my_freebsd_os_sysconf(key):
- if key == 'SC_NPROCESSORS_CONF':
- return NPROCESSORS_FREEBSD
- assert False, 'Unhandled key'
-
-def _my_freebsd_platform_uname():
- return ('FreeBSD', 'freebsd', '8.2-RELEASE', '', 'i386')
-
-def _my_freebsd_osx_subprocess_check_output(command, faked_output={}):
- '''subprocess output shared for freebsd and osx'''
- assert type(command) == list, 'command argument is not a list'
- if command == ['sysctl', '-n', 'kern.boottime']:
- if 'boottime-sysctl' in faked_output:
- return faked_output['boottime-sysctl']
- return bytes('{ sec = ' + str(int(time.time() - 76632)) +
- ', usec = 0 }\n', 'utf-8')
- elif command == ['sysctl', '-n', 'vm.loadavg']:
- return b'{ 0.2 0.4 0.6 }\n'
- else:
- return _my_bsd_subprocess_check_output(command)
-
-def _my_freebsd_subprocess_check_output(command, faked_output):
- assert type(command) == list, 'command argument is not a list'
- if command == ['sysctl', '-n', 'kern.smp.forward_signal_enabled']:
- output = faked_output['smp-sysctl']
- if isinstance(output, Exception):
- raise output
- return output
- elif command == ['vmstat', '-H']:
- return b' procs memory page disks traps cpu\n r b w avm fre flt re pi po fr sr wd0 cd0 int sys cs us sy id\n 0 0 0 343434 123456 47 0 0 0 0 0 2 0 2 80 14 0 1 99\n'
- elif command == ['swapctl', '-s', '-k']:
- return b'Total: 1013216 0\n'
- else:
- freebsd_osx_output = \
- _my_freebsd_osx_subprocess_check_output(command, faked_output)
- if freebsd_osx_output is not None:
- return freebsd_osx_output
- else:
- assert False, 'Unhandled command'
-
-def _my_osx_platform_system():
- return 'Darwin'
-
-def _my_osx_platform_uname():
- return ('Darwin', 'test.example.com', '10.6.0', '', '')
-
-def _my_osx_os_sysconf(key):
- if key == 'SC_NPROCESSORS_CONF':
- return NPROCESSORS_OSX
- assert False, 'Unhandled key'
-
-def _my_osx_subprocess_check_output(command):
- assert type(command) == list, 'command argument is not a list'
- if command == ['sysctl', '-n', 'hw.memsize']:
- # Something different than physmem from bsd
- return b'123456789\n'
- elif command == ['vm_stat']:
- return b'Mach Virtual Memory Statistics: (page size of 4096 bytes)\nPages free: 12345.\nPages speculative: 11111.\n'
- elif command == ['sysctl', '-n', 'vm.swapusage']:
- return b'total = 18432.00M used = 17381.23M free = 1050.77M\n'
- else:
- freebsd_osx_output = _my_freebsd_osx_subprocess_check_output(command)
- if freebsd_osx_output is not None:
- return freebsd_osx_output
- else:
- assert False, 'Unhandled command'
-
-class SysInfoTest(unittest.TestCase):
-
- def setUp(self):
- # Save existing implementations of library functions
- # (they are replaced in the tests)
- self.old_platform_system = platform.system
- self.old_os_sysconf = os.sysconf
- self.old_open = __builtins__.open
- self.old_subprocess_check_output = subprocess.check_output
-
- def tearDown(self):
- # Restore the library functions
- platform.system = self.old_platform_system
- os.sysconf = self.old_os_sysconf
- __builtins__.open = self.old_open
- subprocess.check_output = self.old_subprocess_check_output
-
- def test_sysinfo(self):
- """Test that the various methods on SysInfo exist and return data."""
-
- s = SysInfo()
- self.assertEqual(None, s.get_num_processors())
- self.assertEqual('Unknown', s.get_endianness())
- self.assertEqual('', s.get_platform_hostname())
- self.assertEqual('Unknown', s.get_platform_name())
- self.assertEqual('Unknown', s.get_platform_version())
- self.assertEqual('Unknown', s.get_platform_machine())
- self.assertFalse(s.get_platform_is_smp())
- self.assertEqual(None, s.get_uptime())
- self.assertEqual(None, s.get_uptime_desc())
- self.assertEqual(None, s.get_loadavg())
- self.assertEqual(None, s.get_mem_total())
- self.assertEqual(None, s.get_mem_free())
- self.assertEqual(None, s.get_mem_cached())
- self.assertEqual(None, s.get_mem_buffers())
- self.assertEqual(None, s.get_mem_swap_total())
- self.assertEqual(None, s.get_mem_swap_free())
- self.assertEqual(None, s.get_platform_distro())
- self.assertEqual('Unknown\n', s.get_net_interfaces())
- self.assertEqual('Unknown\n', s.get_net_routing_table())
- self.assertEqual('Unknown\n', s.get_net_stats())
- self.assertEqual('Unknown\n', s.get_net_connections())
-
- def test_sysinfo_factory(self):
- """Test that SysInfoFromFactory returns a valid system-specific
- SysInfo implementation.
-
- See sysinfo.SysInfoTestcase() for some of the parameters.
-
- """
-
- old_platform_system = platform.system
- platform.system = _my_testcase_platform_system
-
- s = SysInfoFromFactory()
- self.assertEqual(None, s.get_num_processors())
- self.assertEqual('bigrastafarian', s.get_endianness())
- self.assertEqual('', s.get_platform_hostname())
- self.assertEqual('b10test', s.get_platform_name())
- self.assertEqual('Unknown', s.get_platform_version())
- self.assertEqual('Unknown', s.get_platform_machine())
- self.assertFalse(s.get_platform_is_smp())
- self.assertEqual(131072, s.get_uptime())
- # We check that we do NOT add 's' to 'day' (because it's singular):
- self.assertEqual('1 day, 12:24:32', s.get_uptime_desc())
- self.assertEqual(None, s.get_loadavg())
- self.assertEqual(None, s.get_mem_total())
- self.assertEqual(None, s.get_mem_free())
- self.assertEqual(None, s.get_mem_cached())
- self.assertEqual(None, s.get_mem_buffers())
- self.assertEqual(None, s.get_mem_swap_total())
- self.assertEqual(None, s.get_mem_swap_free())
- self.assertEqual(None, s.get_platform_distro())
- self.assertEqual('Unknown\n', s.get_net_interfaces())
- self.assertEqual('Unknown\n', s.get_net_routing_table())
- self.assertEqual('Unknown\n', s.get_net_stats())
- self.assertEqual('Unknown\n', s.get_net_connections())
-
- platform.system = old_platform_system
-
- def test_sysinfo_linux(self):
- """Tests the Linux implementation of SysInfo. Note that this
- tests deep into the implementation, and not just the
- interfaces."""
-
- # Replace existing implementations of library functions
- # with mock ones for testing.
- platform.system = _my_linux_platform_system
- os.sysconf = _my_linux_os_sysconf
- __builtins__.open = _my_linux_open
- subprocess.check_output = _my_linux_subprocess_check_output
-
- s = SysInfoFromFactory()
- self.assertEqual(NPROCESSORS_LINUX, s.get_num_processors())
- self.assertEqual('myhostname', s.get_platform_hostname())
- self.assertTrue(s.get_platform_is_smp())
- self.assertEqual(172801, s.get_uptime())
- # We check that we add 's' to 'day', and that the mm part has an
- # additional 0, i.e., not '0:0' but '0:00':
- self.assertEqual('2 days, 0:00:01', s.get_uptime_desc())
- self.assertEqual((0.1, 0.2, 0.3), s.get_loadavg())
- self.assertEqual(3157884928, s.get_mem_total())
- self.assertEqual(891383808, s.get_mem_free())
- self.assertEqual(1335152640, s.get_mem_cached())
- self.assertEqual(28069888, s.get_mem_buffers())
- self.assertEqual(4294963200, s.get_mem_swap_total())
- self.assertEqual(4095451136, s.get_mem_swap_free())
- self.assertEqual('My Distribution', s.get_platform_distro())
-
- # These test that the corresponding tools are being called (and
- # no further processing is done on this data). Please see the
- # implementation functions at the top of this file.
- self.assertEqual('qB2osV6vUOjqm3P/+tQ4d92xoYz8/U8P9v3KWRpNwlI=\n', s.get_net_interfaces())
- self.assertEqual('VGWAS92AlS14Pl2xqENJs5P2Ihe6Nv9g181Mu6Zz+aQ=\n\nXfizswwNA9NkXz6K36ZExpjV08Y5IXkHI8jjDSV+5Nc=\n', s.get_net_routing_table())
- self.assertEqual('osuxbrcc1g9VgaF4yf3FrtfodrfATrbSnjhqhuQSAs8=\n', s.get_net_stats())
- self.assertEqual('Z+w0lwa02/T+5+EIio84rrst/Dtizoz/aL9Im7J7ESA=\n', s.get_net_connections())
-
- def check_bsd_values(self, s):
- # check values shared by all bsd implementations
- self.assertEqual('test.example.com', s.get_platform_hostname())
- self.assertLess(abs(76632 - s.get_uptime()), 4)
- self.assertEqual(None, s.get_mem_cached())
- self.assertEqual(None, s.get_mem_buffers())
- self.assertEqual(None, s.get_platform_distro())
-
- # These test that the corresponding tools are being called (and
- # no further processing is done on this data). Please see the
- # implementation functions at the top of this file.
- self.assertEqual('qB2osV6vUOjqm3P/+tQ4d92xoYz8/U8P9v3KWRpNwlI=\n', s.get_net_interfaces())
- self.assertEqual('XfizswwNA9NkXz6K36ZExpjV08Y5IXkHI8jjDSV+5Nc=\n', s.get_net_routing_table())
- self.assertEqual('osuxbrcc1g9VgaF4yf3FrtfodrfATrbSnjhqhuQSAs8=\n', s.get_net_stats())
- self.assertEqual('Z+w0lwa02/T+5+EIio84rrst/Dtizoz/aL9Im7J7ESA=\n', s.get_net_connections())
-
- def test_sysinfo_openbsd(self):
- """Tests the OpenBSD implementation of SysInfo. Note that this
- tests deep into the implementation, and not just the
- interfaces."""
-
- # Replace existing implementations of library functions
- # with mock ones for testing.
- platform.system = _my_openbsd_platform_system
- os.sysconf = _my_openbsd_os_sysconf
- subprocess.check_output = _my_openbsd_subprocess_check_output
- os.uname = _my_openbsd_platform_uname
-
- s = SysInfoFromFactory()
- self.assertEqual(NPROCESSORS_OPENBSD, s.get_num_processors())
-
- self.check_bsd_values(s)
-
- self.assertEqual((0.7, 0.9, 0.8), s.get_loadavg())
- self.assertFalse(s.get_platform_is_smp())
- self.assertEqual(543214321, s.get_mem_total())
- self.assertEqual(123456 * 1024, s.get_mem_free())
- self.assertEqual(566791168, s.get_mem_swap_total())
- self.assertEqual(566789120, s.get_mem_swap_free())
-
- def test_sysinfo_freebsd(self):
- """Tests the FreeBSD implementation of SysInfo. Note that this
- tests deep into the implementation, and not just the
- interfaces."""
-
- # Replace existing implementations of library functions
- # with mock ones for testing.
- platform.system = _my_freebsd_platform_system
- os.sysconf = _my_freebsd_os_sysconf
-
- # We use a lambda object so we can tweak the subprocess output during
- # the tests later.
- faked_process_output = { 'smp-sysctl': b'1\n' }
- subprocess.check_output = lambda command : \
- _my_freebsd_subprocess_check_output(command, faked_process_output)
-
- os.uname = _my_freebsd_platform_uname
-
- s = SysInfoFromFactory()
- self.assertEqual(NPROCESSORS_FREEBSD, s.get_num_processors())
- self.assertTrue(s.get_platform_is_smp())
-
- # We check the kernel SMP support by the availability of a sysctl
- # variable. The value (especially a 0 value) shouldn't matter.
- faked_process_output['smp-sysctl'] = b'0\n'
- s = SysInfoFromFactory()
- self.assertTrue(s.get_platform_is_smp())
-
- # if the sysctl raises CalledProcessError, we treat it as non-SMP
- # kernel.
- faked_process_output['smp-sysctl'] = \
- subprocess.CalledProcessError(1, 'sysctl')
- s = SysInfoFromFactory()
- self.assertFalse(s.get_platform_is_smp())
-
- # if it results in OSError, no SMP information will be provided.
- faked_process_output['smp-sysctl'] = OSError()
- s = SysInfoFromFactory()
- self.assertIsNone(s.get_platform_is_smp())
-
- self.check_bsd_values(s)
-
- self.assertEqual((0.2, 0.4, 0.6), s.get_loadavg())
- self.assertEqual(543214321, s.get_mem_total())
- self.assertEqual(123456 * 1024, s.get_mem_free())
- self.assertEqual(1037533184, s.get_mem_swap_total())
- self.assertEqual(1037533184, s.get_mem_swap_free())
-
- def test_sysinfo_osx(self):
- """Tests the OS X implementation of SysInfo. Note that this
- tests deep into the implementation, and not just the
- interfaces."""
-
- # Replace existing implementations of library functions
- # with mock ones for testing.
- platform.system = _my_osx_platform_system
- os.sysconf = _my_osx_os_sysconf
- subprocess.check_output = _my_osx_subprocess_check_output
- os.uname = _my_osx_platform_uname
-
- s = SysInfoFromFactory()
- self.assertEqual(NPROCESSORS_OSX, s.get_num_processors())
- self.assertFalse(s.get_platform_is_smp())
-
- self.check_bsd_values(s)
-
- self.assertEqual((0.2, 0.4, 0.6), s.get_loadavg())
- self.assertEqual(123456789, s.get_mem_total())
- self.assertEqual((23456 * 4096), s.get_mem_free())
- self.assertEqual(18874368.0, s.get_mem_swap_total())
- self.assertEqual(1075988.48, s.get_mem_swap_free())
-
-if __name__ == "__main__":
- unittest.main()
+++ /dev/null
-EXTRA_DIST = __init__.py ccsession_mock.py parse_args.py tsigctx_mock.py \
- rrset_utils.py
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-This contains some shared test code for other modules and python processes.
-That's why it doesn't have its own test subdirectory and why it isn't
-installed.
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-# Nothing here, really, it's just to tell python this directory is in
-# module hierarchy
+++ /dev/null
-# Copyright (C) 2012 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-class MockModuleCCSession():
- """Fake ModuleCCSession with a minimal implementation as needed by the
- tests. Currently this module only stores whether some methods have
- been called on it (send_stopping(), and close())"""
- def __init__(self):
- """Will be set to True when send_stopping() is called"""
- self.stopped = False
- """Will be set to True when close() is called"""
- self.closed = False
-
- def send_stopping(self):
- """Fake send_stopping() call. No message is sent, but only stores
- that this method has been called."""
- self.stopped = True
-
- def close(self):
- """Fake close() call. Nothing is closed, but only stores
- that this method has been called."""
- self.closed = True
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-from optparse import OptionParser
-
-class OptsError(Exception):
- """To know when OptionParser would exit"""
- pass
-
-class TestOptParser(OptionParser):
- """
- We define our own option parser to push into the parsing routine.
- This one does not exit the whole application on error, it just raises
- exception. It doesn't change anything else. The application uses the
- stock one.
- """
- def error(self, message):
- raise OptsError(message)
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-'''Utility functions handling DNS RRsets commonly used for tests'''
-
-from isc.dns import *
-
-def rrsets_equal(a, b):
- '''Compare two RRsets, return True if equal, otherwise False
-
- We provide this function as part of test utils as we have no direct rrset
- comparison atm. There's no accessor for sigs either (so this only checks
- name, class, type, ttl, and rdata).
- Also, since we often use fake data in RRSIGs, RRSIG RDATA are not checked.
-
- '''
- return a.get_name() == b.get_name() and \
- a.get_class() == b.get_class() and \
- a.get_type() == b.get_type() and \
- a.get_ttl() == b.get_ttl() and \
- (a.get_type() == RRType.RRSIG or
- sorted(a.get_rdata()) == sorted(b.get_rdata()))
-
-# The following are short cut utilities to create an RRset of a specific
-# RR type with one RDATA. Many of the RR parameters are common in most
-# tests, so we define default values for them for convenience.
-
-def create_a(name, address, ttl=3600):
- rrset = RRset(name, RRClass.IN, RRType.A, RRTTL(ttl))
- rrset.add_rdata(Rdata(RRType.A, RRClass.IN, address))
- return rrset
-
-def create_aaaa(name, address, ttl=3600):
- rrset = RRset(name, RRClass.IN, RRType.AAAA, RRTTL(ttl))
- rrset.add_rdata(Rdata(RRType.AAAA, RRClass.IN, address))
- return rrset
-
-def create_ns(nsname, name=Name('example.com'), ttl=3600):
- '''For convenience we use a default name often used as a zone name'''
- rrset = RRset(name, RRClass.IN, RRType.NS, RRTTL(ttl))
- rrset.add_rdata(Rdata(RRType.NS, RRClass.IN, nsname))
- return rrset
-
-def create_cname(target='target.example.com.', name=Name('example.com'),
- ttl=3600):
- rrset = RRset(name, RRClass.IN, RRType.CNAME, RRTTL(ttl))
- rrset.add_rdata(Rdata(RRType.CNAME, RRClass.IN, target))
- return rrset
-
-def create_generic(name, rdlen, type=RRType('TYPE65300'), ttl=3600):
- '''Create an RR of a general type with an arbitrary length of RDATA
-
- If the RR type isn't specified, type of 65300 will be used, which is
- arbitrarily chosen from the IANA "Reserved for Private Usage" range.
- The RDATA will be filled with specified length of all-0 data.
-
- '''
- rrset = RRset(name, RRClass.IN, type, RRTTL(ttl))
- rrset.add_rdata(Rdata(type, RRClass.IN, '\\# ' +
- str(rdlen) + ' ' + '00' * rdlen))
- return rrset
-
-def create_soa(serial, name=Name('example.com'), ttl=3600):
- '''For convenience we use a default name often used as a zone name'''
-
- rrset = RRset(name, RRClass.IN, RRType.SOA, RRTTL(ttl))
- rdata_str = 'master.example.com. admin.example.com. ' + \
- str(serial) + ' 3600 1800 2419200 7200'
- rrset.add_rdata(Rdata(RRType.SOA, RRClass.IN, rdata_str))
- return rrset
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-from pydnspp import *
-
-class MockTSIGContext(TSIGContext):
- """Tthis is a mock of TSIGContext class for testing.
- Via its "error" attribute, you can fake the result of verify(), thereby
- you can test many of TSIG related tests without requiring actual crypto
- setups. "error" should be either a TSIGError type value or a callable
- object (typically a function). In the latter case, the callable object
- will take the context as a parameter, and is expected to return a
- TSIGError object.
- """
-
- def __init__(self, tsig_key):
- super().__init__(tsig_key)
- self.error = None
- self.verify_called = 0 # number of verify() called
-
- def sign(self, qid, data):
- """Transparently delegate the processing to the super class.
- It doesn't matter much anyway because normal applications that would
- be implemented in Python normally won't call TSIGContext.sign()
- directly.
- """
- return super().sign(qid, data)
-
- def verify(self, tsig_record, data):
- self.verify_called += 1
- # call real "verify" so that we can notice any misue (which would
- # result in exception.
- super().verify(tsig_record, data)
- return self.get_error()
-
- def get_error(self):
- if self.error is None:
- return super().get_error()
- if hasattr(self.error, '__call__'):
- return self.error(self)
- return self.error
-
- def last_had_signature(self):
- return True
+++ /dev/null
-SUBDIRS = . cio tests
-
-python_PYTHON = __init__.py process.py socketserver_mixin.py file.py \
- traceback_handler.py
-BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/util_messages.py
-nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/util_messages.py
-pylogmessagedir = $(pyexecdir)/isc/log_messages/
-python_PYTHON += address_formatter.py
-
-EXTRA_DIST = util_messages.mes
-
-CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/util_messages.py
-CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/util_messages.pyc
-CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/util_messages.pyo
-
-# Define rule to build logging source files from message file
-$(PYTHON_LOGMSGPKG_DIR)/work/util_messages.py: util_messages.mes
- $(top_builddir)/src/lib/log/compiler/message \
- -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/util_messages.mes
-
-pythondir = $(pyexecdir)/isc/util
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-from isc.util.socketserver_mixin import *
+++ /dev/null
-# Copyright (C) 2013 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import socket
-
-class AddressFormatter:
- """
- A utility class to convert an IP address with a port number to a
- string.
-
- It takes a tuple (or list) containing and address string and a port
- number, and optionally a family.
-
- If the family is IPv4, the __str__ output will be
- <address>:<port>
- If the family is IPv6, the __str__ output will be
- [<address>]:<port>
-
- If family is not given, the __str__ method will try to figure it out
- itself, by checking for the ':' character in the address string.
-
- This class is designed to delay the conversion until it's explicitly
- requested, so the conversion doesn't happen if the corresponding log
- message is suppressed because of its log level (which is often the case
- for debug messages).
-
- Note: this optimization comes with the cost of instantiating the
- formatter object itself. It's not really clear which overhead is
- heavier, and we may conclude it's actually better to just generate
- the strings unconditionally. Alternatively, we can make the stored
- address of this object replaceable so that this object can be reused.
- Right now this is an open issue.
-
- See also ClientFormatter in the ddns.logger code, which does something
- similar but based on other criteria (and optional extra value).
- """
- def __init__(self, addr, family=None):
- self.__addr = addr
- self.__family = family
-
- def __addr_v4(self):
- return self.__addr[0] + ':' + str(self.__addr[1])
-
- def __addr_v6(self):
- return '[' + self.__addr[0] + ']:' + str(self.__addr[1])
-
- def __format_addr(self):
- # Some basic sanity checks, should we leave this out for efficiency?
- # (especially strings produce unexpected results)
- if isinstance(self.__addr, str) or\
- not hasattr(self.__addr, "__getitem__"):
- raise ValueError("Address must be a list or tuple")
-
- if self.__family is not None:
- if self.__family == socket.AF_INET6:
- return self.__addr_v6()
- elif self.__family == socket.AF_INET:
- return self.__addr_v4()
- else:
- raise ValueError("Unknown address family: " +
- str(self.__family))
- else:
- if self.__addr[0].find(':') >= 0:
- return self.__addr_v6()
- else:
- return self.__addr_v4()
-
- def __str__(self):
- return self.__format_addr()
-
+++ /dev/null
-SUBDIRS = . tests
-
-AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
-AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CXXFLAGS = $(B10_CXXFLAGS)
-
-# There is a build problem with python3.4 (a new field tp_finalize) has been
-# added and now compiler complains about it not being properly initialized in
-# construtor. Since the whole python thing goes away, it's counter-productive
-# to spend any time on making this work on both python3.3 and 3.4, so
-# ingoring the warning seems the way to go.
-AM_CXXFLAGS += -Wno-error
-
-python_PYTHON = __init__.py
-pythondir = $(PYTHON_SITEPKG_DIR)/isc/util/cio
-
-pyexec_LTLIBRARIES = socketsession.la
-pyexecdir = $(PYTHON_SITEPKG_DIR)/isc/util/cio
-
-socketsession_la_SOURCES = socketsession_python.cc socketsession_python.h
-socketsession_la_SOURCES += socketsessionforwarder_python.cc
-socketsession_la_SOURCES += socketsessionforwarder_python.h
-socketsession_la_SOURCES += socketsessionreceiver_python.cc
-socketsession_la_SOURCES += socketsessionreceiver_python.h
-socketsession_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
-socketsession_la_LDFLAGS = $(PYTHON_LDFLAGS)
-# Note: PYTHON_CXXFLAGS may have some -Wno... workaround, which must be
-# placed after -Wextra defined in AM_CXXFLAGS
-socketsession_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
-
-# Python prefers .so, while some OSes (specifically MacOS) use a different
-# suffix for dynamic objects. -module is necessary to work this around.
-socketsession_la_LDFLAGS += -module -avoid-version
-socketsession_la_LIBADD = $(top_builddir)/src/lib/util/io/libkea-util-io.la
-socketsession_la_LIBADD += $(PYTHON_LIB)
-
-# This is not installed, it helps locate the module during tests
-EXTRA_DIST = __init__.py socketsession.py
-
-EXTRA_DIST += socketsession_inc.cc
-EXTRA_DIST += socketsessionforwarder_inc.cc socketsessionreceiver_inc.cc
-
-CLEANFILES = __init__.pyc socketsession.pyc
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-"""
-Here are function and classes for forwarding socket sessions between processes.
-"""
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-# This file is not installed. See python/isc/log/__init__.py for the trick.
-
-import os
-import sys
-
-for base in sys.path[:]:
- libdir = os.path.join(base, 'isc/util/cio/.libs')
- if os.path.exists(libdir):
- sys.path.insert(0, libdir)
-
-from socketsession import *
+++ /dev/null
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-namespace {
-const char* const socketsession_doc = "\
-This module defines a set of classes that support forwarding a\n\
-\"socket session\" from one process to another. A socket session is a\n\
-conceptual tuple of the following elements:\n\
-\n\
-- A network socket\n\
-- The local and remote endpoints of a (IP) communication taking place\n\
- on the socket. In practice an endpoint is a pair of an IP address\n\
- and TCP or UDP port number.\n\
-- Some amount of data sent from the remote endpoint and received on\n\
- the socket. We call it (socket) session data in this documentation.\n\
-\n\
-Note that this is a conceptual definition. Depending on the underlying\n\
-implementation and/or the network protocol, some of the elements could\n\
-be part of others; for example, if it's an established TCP connection,\n\
-the local and remote endpoints would be able to be retrieved from the\n\
-socket using the standard getsockname() and getpeername() system\n\
-calls. But in this definition we separate these to be more generic.\n\
-Also, as a matter of fact our intended usage includes non-connected\n\
-UDP communications, in which case at least the remote endpoint should\n\
-be provided separately from the socket.\n\
-\n\
-In the actual implementation we represent a socket as a Python socket\n\
-object, which contains the information of the address family\n\
-(e.g. AF_INET6), socket type (e.g. SOCK_STREAM), and protocol\n\
-(e.g. IPPROTO_TCP).\n\
-\n\
-We use the Python socket address tuple to represent endpoints.\n\
-\n\
-Socket session data is an opaque blob in the form of a Python byte\n\
-object.\n\
-\n\
-To forward a socket session between processes, we use connected UNIX\n\
-domain sockets established between the processes. The file descriptor\n\
-will be forwarded through the sockets as an ancillary data item of\n\
-type SCM_RIGHTS. Other elements of the session will be transferred as\n\
-normal data over the connection.\n\
-\n\
-We provide two classes to help applications forward socket sessions:\n\
-SocketSessionForwarder is the sender of the UNIX domain connection,\n\
-while SocketSessionReceiver is the receiver (this interface assumes\n\
-one direction of forwarding).\n\
-\n\
-Note: this paragraph and following discussions on the internal\n\
-protocol are for reference purposes only; it's not necessary to\n\
-understand how to use the API.\n\
-SocketSessionForwarder and SocketSessionReceiver objects (internally)\n\
-use a straightforward protocol to pass elements of socket sessions.\n\
-Once the connection is established, the forwarder object first forwards\n\
-the file descriptor with 1-byte dummy data. It then forwards a\n\
-\"(socket) session header\", which contains all other elements of\n\
-the session except the file descriptor (already forwarded) and session\n\
-data. The wire format of the header is as follows:\n\
-\n\
-- The length of the header (16-bit unsigned integer)\n\
-- Address family\n\
-- Socket type\n\
-- Protocol\n\
-- Size of the local endpoint in bytes\n\
-- Local endpoint (a copy of the memory image of the corresponding\n\
- sockaddr)\n\
-- Size of the remote endpoint in bytes\n\
-- Remote endpoint (same as local endpoint)\n\
-- Size of session data in bytes\n\
-\n\
-The type of the fields is 32-bit unsigned integer unless explicitly\n\
-noted, and all fields are formatted in the network byte order.\n\
-\n\
-The socket session data immediately follows the session header.\n\
-\n\
-Note that the fields do not necessarily be in the network byte order\n\
-because they are expected to be exchanged on the same machine.\n\
-Likewise, integer elements such as address family do not necessarily\n\
-be represented as an fixed-size value (i.e., 32-bit). But fixed size\n\
-fields are used in order to ensure maximum portability in such a\n\
-(rare) case where the forwarder and the receiver are built with\n\
-different compilers that have different definitions of int. Also,\n\
-since sockaddr fields are generally formatted in the network byte\n\
-order, other fields are defined so to be consistent.\n\
-\n\
-One basic assumption in the API of this module is socket sessions\n\
-should be forwarded without blocking, thus eliminating the need for\n\
-incremental read/write or blocking other important services such as\n\
-responding to requests from the application's clients. This assumption\n\
-should be held as long as both the forwarder and receiver have\n\
-sufficient resources to handle the forwarding process since the\n\
-communication is local. But a forward attempt could still block if the\n\
-receiver is busy (or even hang up) and cannot keep up with the volume\n\
-of incoming sessions.\n\
-\n\
-So, in this implementation, the forwarder uses non blocking writes to\n\
-forward sessions. If a write attempt could block, it immediately gives\n\
-up the operation with an exception. The corresponding application is\n\
-expected to catch it, close the connection, and perform any necessary\n\
-recovery for that application (that would normally be re-establish the\n\
-connection with a new receiver, possibly after confirming the\n\
-receiving side is still alive). On the other hand, the receiver\n\
-implementation assumes it's possible that it only receive incomplete\n\
-elements of a session (such as in the case where the forwarder writes\n\
-part of the entire session and gives up the connection). The receiver\n\
-implementation throws an exception when it encounters an incomplete\n\
-session. Like the case of the forwarder application, the receiver\n\
-application is expected to catch it, close the connection, and perform\n\
-any necessary recovery steps.\n\
-\n\
-Note that the receiver implementation uses blocking read. So it's\n\
-application's responsibility to ensure that there's at least some data\n\
-in the connection when the receiver object is requested to receive a\n\
-session (unless this operation can be blocking, e.g., by the use of a\n\
-separate thread). Also, if the forwarder implementation or application\n\
-is malicious or extremely buggy and intentionally sends partial\n\
-session and keeps the connection, the receiver could block in\n\
-receiving a session. In general, we assume the forwarder doesn't do\n\
-intentional blocking as it's a local node and is generally a module of\n\
-the same (BIND 10) system. The minimum requirement for the forwarder\n\
-implementation (and application) is to make sure the connection is\n\
-closed once it detects an error on it. Even a naive implementation\n\
-that simply dies due to the exception will meet this requirement.\n\
-\n\
-";
-} // unnamed namespace
+++ /dev/null
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <Python.h>
-
-#include <util/python/pycppwrapper_util.h>
-
-#include "socketsessionreceiver_python.h"
-#include "socketsessionforwarder_python.h"
-
-using namespace isc::util::io::python;
-using namespace isc::util::python;
-
-#include "socketsession_inc.cc"
-
-namespace isc {
-namespace util {
-namespace io {
-namespace python {
-PyObject* po_SocketSessionError;
-}
-}
-}
-}
-
-namespace {
-
-PyModuleDef socketsession = {
- { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
- "isc.util.cio.socketsession",
- socketsession_doc,
- -1,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL
-};
-} // end of unnamed namespace
-
-PyMODINIT_FUNC
-PyInit_socketsession(void) {
- PyObject* mod = PyModule_Create(&socketsession);
- if (mod == NULL) {
- return (NULL);
- }
-
- try {
- po_SocketSessionError =
- PyErr_NewException("isc.util.cio.SocketSessionError", NULL, NULL);
- PyObjectContainer(po_SocketSessionError).
- installToModule(mod, "SocketSessionError");
- } catch (...) {
- Py_DECREF(mod);
- return (NULL);
- }
-
- if (!initModulePart_SocketSessionForwarder(mod)) {
- Py_DECREF(mod);
- return (NULL);
- }
- if (!initModulePart_SocketSessionReceiver(mod)) {
- Py_DECREF(mod);
- return (NULL);
- }
-
- return (mod);
-}
+++ /dev/null
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef PYTHON_SOCKETSESSION_H
-#define PYTHON_SOCKETSESSION_H 1
-
-#include <Python.h>
-
-namespace isc {
-namespace util {
-namespace io {
-namespace python {
-
-extern PyObject* po_SocketSessionError;
-
-} // namespace python
-} // namespace io
-} // namespace util
-} // namespace isc
-#endif // PYTHON_SOCKETSESSION_H
-
-// Local Variables:
-// mode: c++
-// End:
+++ /dev/null
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-namespace {
-// Modifications:
-// reference to the module description (instead of "utility")
-// exception description
-const char* const SocketSessionForwarder_doc = "\
-The forwarder of socket sessions.\n\
-\n\
-An object of this class maintains a UNIX domain socket (normally\n\
-expected to be connected to a SocketSessionReceiver object) and\n\
-forwards socket sessions to the receiver.\n\
-\n\
-See the description of socketsession module for other details of how\n\
-the session forwarding works.\n\
-\n\
-SocketSessionForwarder(unix_file)\n\
-\n\
- The constructor.\n\
-\n\
- It's constructed with path information of the intended receiver,\n\
- but does not immediately establish a connection to the receiver;\n\
- connect_to_receiver() must be called to establish it. These are\n\
- separated so that an object of class can be initialized (possibly\n\
- as an attribute of a higher level application class object)\n\
- without knowing the receiver is ready for accepting new\n\
- forwarders. The separate connect interface allows the object to be\n\
- reused when it detects connection failure and tries to re-\n\
- establish it after closing the failed one.\n\
-\n\
- On construction, it also installs a signal filter for SIGPIPE to\n\
- ignore it. Since this class uses a stream-type connected UNIX\n\
- domain socket, if the receiver (abruptly) closes the connection a\n\
- subsequent write operation on the socket would trigger a SIGPIPE\n\
- signal, which kills the caller process by default. This behavior\n\
- would be undesirable in many cases, so this implementation always\n\
- disables the signal.\n\
-\n\
- This approach has some drawbacks, however; first, since signal\n\
- handling is process (or thread) wide, ignoring it may not what the\n\
- application wants. On the other hand, if the application changes\n\
- how the signal is handled after instantiating this class, the new\n\
- behavior affects the class operation. Secondly, even if ignoring\n\
- the signal is the desired operation, it's a waste to set the\n\
- filter every time this class object is constructed. It's\n\
- sufficient to do it once. We still adopt this behavior based on\n\
- the observation that in most cases applications would like to\n\
- ignore SIGPIPE (or simply doesn't care about it) and that this\n\
- class is not instantiated so often (so the wasteful setting\n\
- overhead should be marginal). On the other hand, doing it every\n\
- time is beneficial if the application is threaded and different\n\
- threads create different forwarder objects (and if signals work\n\
- per thread).\n\
-\n\
- Exceptions:\n\
- SocketSessionError unix_file is invalid as a path name of a UNIX\n\
- domain socket or error happens in setting a filter for\n\
- SIGPIPE (see above)\n\
- SystemError Unexpected errors such as resource allocation failure\n\
-\n\
- Parameters:\n\
- unix_file Path name of the receiver.\n\
-\n\
-";
-
-// Modifications:
-// exception description
-const char* const SocketSessionForwarder_connectToReceiver_doc = "\
-connect_to_receiver()\n\
-\n\
-Establish a connection to the receiver.\n\
-\n\
-This method establishes a connection to the receiver at the path given\n\
-on construction. It makes the underlying UNIX domain socket non\n\
-blocking, so this method (or subsequent push() calls) does not block.\n\
-\n\
-Exceptions:\n\
- TypeError The method is called while an already established\n\
- connection is still active.\n\
- SocketSessionError A system error in socket operation.\n\
- SystemError Unexpected errors such as resource allocation failure\n\
-\n\
-";
-
-// Modifications:
-// bullet description
-// parameters
-// exception description
-const char* const SocketSessionForwarder_push_doc = "\
-push(sock, family, type, protocol, local_end, remote_end, data)\n\
-\n\
-Forward a socket session to the receiver.\n\
-\n\
-This method takes a set of parameters that represent a single socket\n\
-session, renders them in the \"wire\" format according to the internal\n\
-protocol (see socketsession module) and forwards them to the\n\
-receiver through the UNIX domain connection.\n\
-\n\
-The connection must have been established by connect_to_receiver().\n\
-\n\
-For simplicity and for the convenience of detecting application\n\
-errors, this method imposes some restrictions on the parameters:\n\
-\n\
-- Socket family must be either AF_INET or AF_INET6\n\
-- The address family (sa_family) member of the local and remote end\n\
- points must be equal to the family parameter\n\
-- Socket session data must not be empty\n\
-- Data length must not exceed 65535\n\
-\n\
-These are not architectural limitation, and might be loosened in future\n\
-versions as we see the need for flexibility.\n\
-\n\
-Since the underlying UNIX domain socket is non blocking (see the\n\
-description for the constructor), a call to this method should either\n\
-return immediately or result in exception (in case of \"would\n\
-block\").\n\
-\n\
-Exceptions:\n\
- TypeError The method is called before establishing a connection or\n\
- given parameters are invalid, or the given socket address\n\
- is valid.\n\
- SocketSessionError A system error in socket operation, including the\n\
- case where the write operation would block.\n\
-\n\
-Parameters:\n\
- sock (int) The socket file descriptor\n\
- family (int) The address family (such as socket.AF_INET6) of the\n\
- socket\n\
- type (int) The socket type (such as socket.SOCK_DGRAM) of the\n\
- socket\n\
- protocol (int) The transport protocol (such as socket.IPPROTO_UDP)\n\
- of the socket\n\
- local_end (socket address) The local end point of the session\n\
- remote_end (socket address) The remote end point of the session\n\
- data (byte) the session data\n\
-\n\
-";
-} // unnamed namespace
+++ /dev/null
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-// Enable this if you use s# variants with PyArg_ParseTuple(), see
-// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
-//#define PY_SSIZE_T_CLEAN
-
-// Python.h needs to be placed at the head of the program file, see:
-// http://docs.python.org/py3k/extending/extending.html#a-simple-example
-#include <Python.h>
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netdb.h>
-
-#include <string>
-#include <stdexcept>
-
-#include <boost/lexical_cast.hpp>
-
-#include <exceptions/exceptions.h>
-
-#include <util/io/sockaddr_util.h>
-#include <util/io/socketsession.h>
-#include <util/python/pycppwrapper_util.h>
-
-#include "socketsession_python.h"
-#include "socketsessionforwarder_python.h"
-
-using namespace std;
-using namespace isc::util::python;
-using namespace isc::util::io;
-using namespace isc::util::io::internal;
-using namespace isc::util::io::python;
-using boost::lexical_cast;
-
-// Trivial constructor.
-s_SocketSessionForwarder::s_SocketSessionForwarder() : cppobj(NULL) {
-}
-
-// Import pydoc text
-#include "socketsessionforwarder_inc.cc"
-
-// See python/isc/log/log.cc for the use of namespace
-namespace clang_unnamed_namespace_workaround {
-// Internal exception class thrown when address parsing fails
-class AddressParseError: public isc::Exception {
-public:
- AddressParseError(const char *file, size_t line, const char *what):
- isc::Exception(file, line, what) {}
-};
-}
-using namespace clang_unnamed_namespace_workaround;
-
-namespace {
-
-int
-SocketSessionForwarder_init(PyObject* po_self, PyObject* args, PyObject*) {
- s_SocketSessionForwarder* self =
- static_cast<s_SocketSessionForwarder*>(po_self);
- try {
- const char* unix_file;
- if (PyArg_ParseTuple(args, "s", &unix_file)) {
- self->cppobj = new SocketSessionForwarder(unix_file);
- return (0);
- }
- } catch (const exception& ex) {
- const string ex_what =
- "Failed to construct SocketSessionForwarder object: " +
- string(ex.what());
- PyErr_SetString(po_SocketSessionError, ex_what.c_str());
- return (-1);
- } catch (...) {
- PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
- return (-1);
- }
-
- return (-1);
-}
-
-void
-SocketSessionForwarder_destroy(PyObject* po_self) {
- s_SocketSessionForwarder* self =
- static_cast<s_SocketSessionForwarder*>(po_self);
- delete self->cppobj;
- self->cppobj = NULL;
- Py_TYPE(self)->tp_free(self);
-}
-
-// Convert a Python socket address object to an addrinfo structure by
-// getaddrinfo.
-void
-parsePySocketAddress(PyObject* obj, int type, int protocol,
- struct sockaddr_storage* ss)
-{
- struct addrinfo hints;
- memset(&hints, 0, sizeof(hints));
- hints.ai_socktype = type;
- hints.ai_protocol = protocol;
- hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
-
- const char* addr;
- int port, flowinfo, scopeid;
- struct addrinfo *res;
- if (PyArg_ParseTuple(obj, "si", &addr, &port)) {
- // Possibly an IPv4 address.
- hints.ai_family = AF_INET;
- const int error = getaddrinfo(addr,
- lexical_cast<string>(port).c_str(),
- &hints, &res);
- if (error == 0) {
- assert(res->ai_addrlen <= sizeof(*ss));
- memcpy(ss, res->ai_addr, res->ai_addrlen);
- return;
- }
- isc_throw(AddressParseError, "Invalid or unsupported socket address: "
- << gai_strerror(error));
- }
- PyErr_Clear();
- if (PyArg_ParseTuple(obj, "siii", &addr, &port, &flowinfo, &scopeid)) {
- // Possibly an IPv6 address. We ignore flowinfo.
- hints.ai_family = AF_INET6;
- const int error = getaddrinfo(addr,
- lexical_cast<string>(port).c_str(),
- &hints, &res);
- if (error == 0) {
- assert(res->ai_addrlen <= sizeof(*ss));
- memcpy(ss, res->ai_addr, res->ai_addrlen);
- void* p = ss;
- static_cast<struct sockaddr_in6*>(p)->sin6_scope_id = scopeid;
- return;
- }
- isc_throw(AddressParseError, "Invalid or unsupported socket address: "
- << gai_strerror(error));
- }
- PyErr_Clear();
- isc_throw(AddressParseError, "Invalid or unsupported socket address, must "
- "be AF_INET or AF_INET6 socket address.");
-}
-
-PyObject*
-SocketSessionForwarder_connectToReceiver(PyObject* po_self, PyObject*) {
- s_SocketSessionForwarder* const self =
- static_cast<s_SocketSessionForwarder*>(po_self);
-
- try {
- self->cppobj->connectToReceiver();
- Py_RETURN_NONE;
- } catch (const isc::BadValue& ex) {
- PyErr_SetString(PyExc_TypeError, ex.what());
- return (NULL);
- } catch (const SocketSessionError& ex) {
- PyErr_SetString(po_SocketSessionError, ex.what());
- return (NULL);
- } catch (const exception& ex) {
- const string ex_what =
- "Unexpected failure in connecting to receiver: " +
- string(ex.what());
- PyErr_SetString(PyExc_SystemError, ex_what.c_str());
- return (NULL);
- } catch (...) {
- PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
- return (NULL);
- }
-}
-
-PyObject*
-SocketSessionForwarder_push(PyObject* po_self, PyObject* args) {
- s_SocketSessionForwarder* const self =
- static_cast<s_SocketSessionForwarder*>(po_self);
-
- try {
- int fd, family, type, protocol;
- PyObject* po_local_end;
- PyObject* po_remote_end;
- Py_buffer py_buf;
-
- if (!PyArg_ParseTuple(args, "iiiiOOy*", &fd, &family, &type, &protocol,
- &po_local_end, &po_remote_end, &py_buf)) {
- return (NULL);
- }
- struct sockaddr_storage ss_local, ss_remote;
- parsePySocketAddress(po_local_end, type, protocol, &ss_local);
- parsePySocketAddress(po_remote_end, type, protocol, &ss_remote);
- self->cppobj->push(fd, family, type, protocol,
- *convertSockAddr(&ss_local),
- *convertSockAddr(&ss_remote),
- py_buf.buf, py_buf.len);
- Py_RETURN_NONE;
- } catch (const AddressParseError& ex) {
- PyErr_SetString(PyExc_TypeError, ex.what());
- return (NULL);
- } catch (const isc::BadValue& ex) {
- PyErr_SetString(PyExc_TypeError, ex.what());
- return (NULL);
- } catch (const SocketSessionError& ex) {
- PyErr_SetString(po_SocketSessionError, ex.what());
- return (NULL);
- } catch (...) {
- PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
- return (NULL);
- }
-}
-
-// This list contains the actual set of functions we have in
-// python. Each entry has
-// 1. Python method name
-// 2. Our static function here
-// 3. Argument type
-// 4. Documentation
-PyMethodDef SocketSessionForwarder_methods[] = {
- { "push", SocketSessionForwarder_push, METH_VARARGS,
- SocketSessionForwarder_push_doc },
- { "connect_to_receiver", SocketSessionForwarder_connectToReceiver,
- METH_NOARGS, SocketSessionForwarder_connectToReceiver_doc },
- { NULL, NULL, 0, NULL }
-};
-} // end of unnamed namespace
-
-namespace isc {
-namespace util {
-namespace io {
-namespace python {
-// This defines the complete type for reflection in python and
-// parsing of PyObject* to s_SocketSessionForwarder
-// Most of the functions are not actually implemented and NULL here.
-PyTypeObject socketsessionforwarder_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "isc.util.cio.SocketSessionForwarder",
- sizeof(s_SocketSessionForwarder), // tp_basicsize
- 0, // tp_itemsize
- SocketSessionForwarder_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- NULL, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- SocketSessionForwarder_doc,
- NULL, // tp_traverse
- NULL, // tp_clear
- NULL, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- SocketSessionForwarder_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- SocketSessionForwarder_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-// Module Initialization, all statics are initialized here
-bool
-initModulePart_SocketSessionForwarder(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&socketsessionforwarder_type) < 0) {
- return (false);
- }
- void* p = &socketsessionforwarder_type;
- if (PyModule_AddObject(mod, "SocketSessionForwarder",
- static_cast<PyObject*>(p)) < 0) {
- return (false);
- }
- Py_INCREF(&socketsessionforwarder_type);
-
- return (true);
-}
-} // namespace python
-} // namespace io
-} // namespace util
-} // namespace isc
+++ /dev/null
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef PYTHON_SOCKETSESSIONFORWARDER_H
-#define PYTHON_SOCKETSESSIONFORWARDER_H 1
-
-#include <Python.h>
-
-namespace isc {
-namespace util {
-namespace io {
-class SocketSessionForwarder;
-
-namespace python {
-
-// The s_* Class simply covers one instantiation of the object
-class s_SocketSessionForwarder : public PyObject {
-public:
- s_SocketSessionForwarder();
- SocketSessionForwarder* cppobj;
-};
-
-extern PyTypeObject socketsessionforwarder_type;
-
-bool initModulePart_SocketSessionForwarder(PyObject* mod);
-} // namespace python
-} // namespace io
-} // namespace util
-} // namespace isc
-#endif // PYTHON_SOCKETSESSIONFORWARDER_H
-
-// Local Variables:
-// mode: c++
-// End:
+++ /dev/null
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-namespace {
-// Modifications
-// - about return value
-// - socket session "utility" => module
-const char* const SocketSessionReceiver_doc = "\
-The receiver of socket sessions.\n\
-\n\
-An object of this class holds a UNIX domain socket for an established\n\
-connection, receives socket sessions from the remote forwarder, and\n\
-provides the session to the application as a tuple of corresponding\n\
-elements.\n\
-\n\
-Note that this class is instantiated with an already connected socket;\n\
-it's not a listening socket that is accepting connection requests from\n\
-forwarders. It's application's responsibility to create the listening\n\
-socket, listen on it and accept connections. Once the connection is\n\
-established, the application would construct a SocketSessionReceiver\n\
-object with the socket for the newly established connection. This\n\
-behavior is based on the design decision that the application should\n\
-decide when it performs (possibly) blocking operations (see\n\
-socketsession module for more details).\n\
-\n\
-See the description of socketsession module for other details of how\n\
-the session forwarding works.\n\
-\n\
-SocketSessionReceiver(socket)\n\
-\n\
- The constructor.\n\
-\n\
- Exceptions:\n\
- TypeError The given parameter is not a valid socket object\n\
- SocketSessionError Any error on an operation that is performed\n\
- on the given socket as part of initialization.\n\
- SystemError Unexpected errors such as resource allocation failure\n\
-\n\
- Parameters:\n\
- socket A python socket object of a UNIX domain family for an\n\
- established connection with a forwarder.\n\
-\n\
-";
-
-// Modifications
-// - socket session utility -> module
-// - return value (not a SocketSession object, but a Python tuple)
-// - remove the validity note (we copy it here, so there's no such
-// restriction)
-// - caller's responsibility: only responsible for closing the socket.
-// - text around the bullets
-// - exception
-const char* const SocketSessionReceiver_pop_doc = "\
-pop() -> (socket, socket address, socket address, byte)\n\
-\n\
-Receive a socket session from the forwarder.\n\
-\n\
-This method receives wire-format data (see socketsession module) for\n\
-a socket session on the UNIX domain socket, performs some validation\n\
-on the data, and returns the session information as a tuple.\n\
-\n\
-The caller is responsible for closing the received socket.\n\
-\n\
-It ensures the following:\n\
-\n\
-- The socket's address family is either AF_INET or AF_INET6\n\
-- The family element of the socket addresses for the local and remote\n\
- end points must be equal to the socket's address family\n\
-- The socket session data is not empty and does not exceed 65535\n\
- bytes.\n\
-\n\
-If the validation fails or an unexpected system error happens\n\
-(including a connection close in the meddle of reception), it throws\n\
-an SocketSessionError exception. When this happens, it's very\n\
-unlikely that a subsequent call to this method succeeds, so in\n\
-reality the application is expected to destruct it and close the\n\
-socket in such a case.\n\
-\n\
-Exceptions:\n\
- SocketSessionError Invalid data is received or a system error on\n\
- socket operation happens.\n\
- SystemError Unexpected errors such as resource allocation failure\n\
-\n\
-Return Value(s): A tuple corresponding to the extracted socket session:\n\
- socket A Python socket object corresponding to the socket passed\n\
- by the forwarder\n\
- socket address A Python socket address (which is a tuple) for the local\n\
- end point\n\
- socket address A Python socket address for the remote endpoint\n\
- data A Python byte object that stores the session data\n\
-";
-} // unnamed namespace
+++ /dev/null
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-// Enable this if you use s# variants with PyArg_ParseTuple(), see
-// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
-//#define PY_SSIZE_T_CLEAN
-
-// Python.h needs to be placed at the head of the program file, see:
-// http://docs.python.org/py3k/extending/extending.html#a-simple-example
-#include <Python.h>
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netdb.h>
-
-#include <string>
-#include <stdexcept>
-
-#include <boost/lexical_cast.hpp>
-
-#include <util/python/pycppwrapper_util.h>
-
-#include <util/io/socketsession.h>
-
-#include "socketsession_python.h"
-#include "socketsessionreceiver_python.h"
-
-using namespace std;
-using namespace isc::util::python;
-using namespace isc::util::io;
-using namespace isc::util::io::python;
-using boost::lexical_cast;
-
-// Trivial constructor.
-s_SocketSessionReceiver::s_SocketSessionReceiver() : cppobj(NULL) {
-}
-
-// Import pydoc text
-#include "socketsessionreceiver_inc.cc"
-
-namespace {
-// This C structure corresponds to a Python callable object for
-// socket.fromfd().
-// See json_dumps_obj in dns_requestloader_python.cc for background rationale
-// of this trick.
-PyObject* socket_fromfd_obj = NULL;
-
-int
-SocketSessionReceiver_init(PyObject* po_self, PyObject* args, PyObject*) {
- s_SocketSessionReceiver* self =
- static_cast<s_SocketSessionReceiver*>(po_self);
- try {
- // The constructor expects a Python socket object. We'll extract
- // the underlying file descriptor using the fileno method (in the
- // duck typing manner) and pass it to the C++ constructor.
- // PyObject_CallMethod() could return NULL (especially if the given
- // object is of the wrong type and doesn't have the "fileno" method),
- // in which case PyObjectContainer will detect it and throw
- // PyCPPWrapperException, which will be converted to the Python
- // TypeError below.
- PyObject* po_sock;
- if (PyArg_ParseTuple(args, "O", &po_sock)) {
- PyObjectContainer fd_container(PyObject_CallMethod(
- po_sock,
- const_cast<char*>("fileno"),
- NULL));
- PyObjectContainer fdarg_container(
- Py_BuildValue("(O)", fd_container.get()));
- int fd;
- if (PyArg_ParseTuple(fdarg_container.get(), "i", &fd)) {
- self->cppobj = new SocketSessionReceiver(fd);
- return (0);
- }
- PyErr_SetString(PyExc_TypeError, "Given object's fileno() doesn't "
- "return an integer, probably not a valid socket "
- "object");
- }
- } catch (const PyCPPWrapperException& ex) {
- // This could happen due to memory allocation failure, but it's more
- // likely that the object doesn't have the "fileno()" method or it
- // returns an unexpected type of value. So we adjust the error
- // message accordingly.
- PyErr_SetString(PyExc_TypeError, "Failed to parse parameter, "
- "probably not a valid socket object");
- } catch (const exception& ex) {
- const string ex_what =
- "Failed to construct SocketSessionReceiver object: " +
- string(ex.what());
- PyErr_SetString(po_SocketSessionError, ex_what.c_str());
- } catch (...) {
- PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
- }
-
- return (-1);
-}
-
-PyObject*
-createPySocketAddress(const struct sockaddr& sa) {
- socklen_t salen;
- if (sa.sa_family == AF_INET) {
- salen = sizeof(struct sockaddr_in);
- } else if (sa.sa_family == AF_INET6) {
- salen = sizeof(struct sockaddr_in6);
- } else {
- isc_throw(SocketSessionError, "Unsupported socket address family: "
- << static_cast<int>(sa.sa_family));
- }
-
- char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
- const int error = getnameinfo(&sa, salen, hbuf, sizeof(hbuf), sbuf,
- sizeof(sbuf),
- NI_NUMERICHOST | NI_NUMERICSERV);
- if (error != 0) {
- isc_throw(SocketSessionError, "Unrecognized socket address format: "
- << gai_strerror(error));
- }
- if (sa.sa_family == AF_INET) {
- return (Py_BuildValue("(si)", hbuf, lexical_cast<int>(sbuf)));
- }
- // We know it's AF_INET6 at this point. We need some special trick for
- // non-0 scope (zone) ID: getnameinfo() may convert the address to a
- // textual representation using the extension described in RFC 4007,
- // in which case it contains a delimiter character '%'. We need to remove
- // it before constructing the tuple. The scope (zone) ID is preserved
- // in the corresponding field of the tuple.
- const void* p = &sa;
- const struct sockaddr_in6* sin6 =
- static_cast<const struct sockaddr_in6*>(p);
- char* cp = strchr(hbuf, '%');
- if (cp != NULL) {
- *cp = '\0';
- }
- return (Py_BuildValue("(siii)", hbuf, lexical_cast<int>(sbuf), 0,
- sin6->sin6_scope_id));
-}
-
-void
-SocketSessionReceiver_destroy(PyObject* po_self) {
- s_SocketSessionReceiver* self =
- static_cast<s_SocketSessionReceiver*>(po_self);
- delete self->cppobj;
- self->cppobj = NULL;
- Py_TYPE(self)->tp_free(self);
-}
-
-// A helper struct to automatically close a socket in an RAII manner.
-struct ScopedSocket : boost::noncopyable {
- ScopedSocket(int fd) : fd_(fd) {}
- ~ScopedSocket() {
- close(fd_);
- }
- const int fd_;
-};
-
-PyObject*
-SocketSessionReceiver_pop(PyObject* po_self, PyObject*) {
- s_SocketSessionReceiver* const self =
- static_cast<s_SocketSessionReceiver*>(po_self);
-
- try {
- // retrieve the session, and the convert it to a corresponding
- // Python tuple.
- const SocketSession session = self->cppobj->pop();
-
- // We need to immediately store the socket file descriptor in a
- // ScopedSocket object. socket.fromfd() will dup() the FD, so we need
- // to close our copy even if an exception is thrown.
- ScopedSocket sock(session.getSocket());
-
- // Build Python socket object
- PyObjectContainer c_args(Py_BuildValue("(iiii)", sock.fd_,
- session.getFamily(),
- session.getType(),
- session.getProtocol()));
- PyObjectContainer c_sock(PyObject_CallObject(socket_fromfd_obj,
- c_args.get()));
- // Convert the local and remote sockaddr to Python socket address objs
- PyObjectContainer c_local(createPySocketAddress(
- session.getLocalEndpoint()));
- PyObjectContainer c_remote(createPySocketAddress(
- session.getRemoteEndpoint()));
- // Convert the session data to Python byte object.
- PyObjectContainer c_data(Py_BuildValue("y#", session.getData(),
- session.getDataLength()));
-
- // Build a tuple from them and return it.
- return (Py_BuildValue("(OOOO)", c_sock.get(), c_local.get(),
- c_remote.get(), c_data.get()));
- } catch (const SocketSessionError& ex) {
- PyErr_SetString(po_SocketSessionError, ex.what());
- return (NULL);
- } catch (const exception& ex) {
- const string ex_what =
- "Unexpected failure in receiving a socket session: " +
- string(ex.what());
- PyErr_SetString(PyExc_SystemError, ex_what.c_str());
- return (NULL);
- } catch (...) {
- PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
- return (NULL);
- }
-}
-
-// These are the functions we export
-
-// This list contains the actual set of functions we have in
-// python. Each entry has
-// 1. Python method name
-// 2. Our static function here
-// 3. Argument type
-// 4. Documentation
-PyMethodDef SocketSessionReceiver_methods[] = {
- { "pop", SocketSessionReceiver_pop, METH_NOARGS,
- SocketSessionReceiver_pop_doc },
- { NULL, NULL, 0, NULL }
-};
-} // end of unnamed namespace
-
-namespace isc {
-namespace util {
-namespace io {
-namespace python {
-// This defines the complete type for reflection in python and
-// parsing of PyObject* to s_SocketSessionReceiver
-// Most of the functions are not actually implemented and NULL here.
-PyTypeObject socketsessionreceiver_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "isc.util.cio.SocketSessionReceiver",
- sizeof(s_SocketSessionReceiver), // tp_basicsize
- 0, // tp_itemsize
- SocketSessionReceiver_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- NULL, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- SocketSessionReceiver_doc,
- NULL, // tp_traverse
- NULL, // tp_clear
- NULL, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- SocketSessionReceiver_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- SocketSessionReceiver_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-// Module Initialization, all statics are initialized here
-bool
-initModulePart_SocketSessionReceiver(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&socketsessionreceiver_type) < 0) {
- return (false);
- }
- void* p = &socketsessionreceiver_type;
- if (PyModule_AddObject(mod, "SocketSessionReceiver",
- static_cast<PyObject*>(p)) < 0) {
- return (false);
- }
-
- PyObject* socket_module = PyImport_AddModule("socket");
- if (socket_module != NULL) {
- PyObject* socket_dict = PyModule_GetDict(socket_module);
- if (socket_dict != NULL) {
- socket_fromfd_obj = PyDict_GetItemString(socket_dict, "fromfd");
- }
- }
- if (socket_fromfd_obj != NULL) {
- Py_INCREF(socket_fromfd_obj);
- } else {
- PyErr_SetString(PyExc_RuntimeError,
- "isc.util.cio.SocketSessionReceiver needs "
- "socket.fromfd(), but it's missing");
- return (false);
- }
-
- Py_INCREF(&socketsessionreceiver_type);
-
- return (true);
-}
-
-} // namespace python
-} // namespace io
-} // namespace util
-} // namespace isc
+++ /dev/null
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef PYTHON_SOCKETSESSIONRECEIVER_H
-#define PYTHON_SOCKETSESSIONRECEIVER_H 1
-
-#include <Python.h>
-
-namespace isc {
-namespace util {
-namespace io {
-class SocketSessionReceiver;
-
-namespace python {
-
-// The s_* Class simply covers one instantiation of the object
-class s_SocketSessionReceiver : public PyObject {
-public:
- s_SocketSessionReceiver();
- SocketSessionReceiver* cppobj;
-};
-
-extern PyTypeObject socketsessionreceiver_type;
-
-bool initModulePart_SocketSessionReceiver(PyObject* mod);
-
-} // namespace io
-} // namespace python
-} // namespace util
-} // namespace isc
-#endif // PYTHON_SOCKETSESSIONRECEIVER_H
-
-// Local Variables:
-// mode: c++
-// End:
+++ /dev/null
-PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = socketsession_test.py
-EXTRA_DIST = $(PYTESTS)
-
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
-LIBRARY_PATH_PLACEHOLDER =
-if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
-endif
-
-# test using command-line arguments, so use check-local target instead of TESTS
-check-local:
-if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
- rm -f .coverage
- ${LN_S} $(abs_top_srcdir)/.coverage .coverage
-endif
-# Note: below we intentionally use a non absolute path for TESTDATAOBJDIR.
-# It will be used as part of the path for a UNIX domain socket. Due to the
-# relatively lower limit on the length it's better to keep it as short as
-# possible.
- for pytest in $(PYTESTS) ; do \
- echo Running test: $$pytest ; \
- PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/isc/python/util/io/.libs \
- $(LIBRARY_PATH_PLACEHOLDER) \
- TESTDATAOBJDIR=$(builddir) \
- $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
- done
-
-CLEANFILES = $(builddir)/ssessiontest.unix
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import os, signal, socket, unittest
-from socket import AF_INET, AF_INET6, SOCK_STREAM, SOCK_DGRAM, IPPROTO_UDP, \
- IPPROTO_TCP
-from isc.util.cio.socketsession import *
-
-TESTDATA_OBJDIR = os.getenv("TESTDATAOBJDIR")
-TEST_UNIX_FILE = TESTDATA_OBJDIR + '/ssessiontest.unix'
-TEST_DATA = b'BIND10 test'
-TEST_PORT = 53535
-TEST_PORT2 = 53536
-TEST_PORT3 = 53537
-
-class TestForwarder(unittest.TestCase):
- '''In general, this is a straightforward port of the C++ counterpart.
-
- In some cases test cases are simplified or have Python specific cases.
-
- '''
-
- def setUp(self):
- self.listen_sock = None
- self.forwarder = SocketSessionForwarder(TEST_UNIX_FILE)
- if os.path.exists(TEST_UNIX_FILE):
- os.unlink(TEST_UNIX_FILE)
- self.large_text = b'a' * 65535
-
- def tearDown(self):
- if self.listen_sock is not None:
- self.listen_sock.close()
- if os.path.exists(TEST_UNIX_FILE):
- os.unlink(TEST_UNIX_FILE)
-
- def start_listen(self):
- self.listen_sock = socket.socket(socket.AF_UNIX, SOCK_STREAM, 0)
- self.listen_sock.bind(TEST_UNIX_FILE)
- self.listen_sock.listen(10)
-
- def accept_forwarder(self):
- self.listen_sock.setblocking(False)
- s, _ = self.listen_sock.accept()
- s.setblocking(True)
- return s
-
- def test_init(self):
- # check bad arguments. valid cases will covered in other tests.
- self.assertRaises(TypeError, SocketSessionForwarder, 1)
- self.assertRaises(TypeError, SocketSessionForwarder,
- 'test.unix', 'test.unix')
-
- def test_badpush(self):
- # bad numbers of parameters
- self.assertRaises(TypeError, self.forwarder.push, 1)
- self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('127.0.0.1', 53),
- ('192.0.2.1', 5300), TEST_DATA, 0)
- # contain a bad type of parameter
- self.assertRaises(TypeError, self.forwarder.push, 0, 'AF_INET',
- SOCK_DGRAM, IPPROTO_UDP, ('127.0.0.1', 53),
- ('192.0.2.1', 5300), TEST_DATA)
- # bad local address
- self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('127.0.0..1', 53),
- ('192.0.2.1', 5300), TEST_DATA)
- self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, '127.0.0.1',
- ('192.0.2.1', 5300), TEST_DATA)
- # bad remote address
- self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET6,
- SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53),
- ('2001:db8:::3', 5300), TEST_DATA)
-
- # push before connect
- self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
- ('192.0.2.2', 53), TEST_DATA)
-
- # Now connect the forwarder for the rest of tests
- self.start_listen()
- self.forwarder.connect_to_receiver()
-
- # Inconsistent address family
- self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53, 0, 1),
- ('192.0.2.2', 53), TEST_DATA)
- self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET6,
- SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53, 0, 1),
- ('192.0.2.2', 53), TEST_DATA)
-
- # Empty data: we reject them at least for now
- self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
- ('192.0.2.2', 53), b'')
-
- # Too big data: we reject them at least for now
- self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
- ('192.0.2.2', 53), b'd' * 65536)
-
- # Close the receptor before push. It will result in SIGPIPE (should be
- # ignored) and EPIPE, which will be converted to SocketSessionError.
- self.listen_sock.close()
- self.assertRaises(SocketSessionError, self.forwarder.push, 1, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
- ('192.0.2.2', 53), TEST_DATA)
-
- def create_socket(self, family, type, protocol, addr, do_listen):
- s = socket.socket(family, type, protocol)
- s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- s.bind(addr)
- if do_listen and protocol == IPPROTO_TCP:
- s.listen(1)
- return s
-
- def check_push_and_pop(self, family, type, protocol, local, remote,
- data, new_connection):
- sock = self.create_socket(family, type, protocol, local, True)
- fwd_fd = sock.fileno()
- if protocol == IPPROTO_TCP:
- client_addr = ('::1', 0, 0, 0) if family == AF_INET6 \
- else ('127.0.0.1', 0)
- client_sock = self.create_socket(family, type, protocol,
- client_addr, False)
- client_sock.setblocking(False)
- try:
- client_sock.connect(local)
- except socket.error:
- pass
- server_sock, _ = sock.accept()
- fwd_fd = server_sock.fileno()
-
- # If a new connection is required, start the "server", have the
- # internal forwarder connect to it, and then internally accept it.
- if new_connection:
- self.start_listen()
- self.forwarder.connect_to_receiver()
- self.accept_sock = self.accept_forwarder()
-
- # Then push one socket session via the forwarder.
- self.forwarder.push(fwd_fd, family, type, protocol, local, remote,
- data)
-
- # Pop the socket session we just pushed from a local receiver, and
- # check the content.
- receiver = SocketSessionReceiver(self.accept_sock)
- signal.alarm(1)
- sock_session = receiver.pop()
- signal.alarm(0)
- passed_sock = sock_session[0]
- self.assertNotEqual(fwd_fd, passed_sock.fileno())
- self.assertEqual(family, passed_sock.family)
- self.assertEqual(type, passed_sock.type)
- self.assertEqual(protocol, passed_sock.proto)
- self.assertEqual(local, sock_session[1])
- self.assertEqual(remote, sock_session[2])
- self.assertEqual(data, sock_session[3])
-
- # Check if the passed FD is usable by sending some data from it.
- passed_sock.setblocking(True)
- if protocol == IPPROTO_UDP:
- self.assertEqual(len(TEST_DATA), passed_sock.sendto(TEST_DATA,
- local))
- sock.settimeout(10)
- self.assertEqual(TEST_DATA, sock.recvfrom(len(TEST_DATA))[0])
- else:
- self.assertEqual(len(TEST_DATA), passed_sock.send(TEST_DATA))
- client_sock.setblocking(True)
- client_sock.settimeout(10)
- self.assertEqual(TEST_DATA, client_sock.recv(len(TEST_DATA)))
- server_sock.close()
- client_sock.close()
-
- passed_sock.close()
- sock.close()
-
- def test_push_and_pop(self):
- # This is a straightforward port of C++ pushAndPop test. See the
- # C++ version why we use multiple ports for "local".
- local6 = ('::1', TEST_PORT, 0, 0)
- local6_alt = ('::1', TEST_PORT2, 0, 0)
- local6_alt2 = ('::1', TEST_PORT3, 0, 0)
- remote6 = ('2001:db8::1', 5300, 0, 0)
- self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
- local6, remote6, TEST_DATA, True)
- self.check_push_and_pop(AF_INET6, SOCK_STREAM, IPPROTO_TCP,
- local6, remote6, TEST_DATA, False)
-
- local4 = ('127.0.0.1', TEST_PORT)
- local4_alt = ('127.0.0.1', TEST_PORT2)
- remote4 = ('192.0.2.2', 5300)
- self.check_push_and_pop(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
- local4, remote4, TEST_DATA, False)
- self.check_push_and_pop(AF_INET, SOCK_STREAM, IPPROTO_TCP,
- local4, remote4, TEST_DATA, False)
-
- self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
- local6_alt, remote6, self.large_text, False)
- self.check_push_and_pop(AF_INET6, SOCK_STREAM, IPPROTO_TCP,
- local6, remote6, self.large_text, False)
- self.check_push_and_pop(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
- local4_alt, remote4, self.large_text, False)
- self.check_push_and_pop(AF_INET, SOCK_STREAM, IPPROTO_TCP,
- local4, remote4, self.large_text, False)
-
- # Python specific: check for an IPv6 scoped address with non 0
- # scope (zone) ID
- scope6 = ('fe80::1', TEST_PORT, 0, 1)
- self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
- local6_alt2, scope6, TEST_DATA, False)
-
- def test_push_too_fast(self):
- # A straightforward port of C++ pushTooFast test.
- def multi_push(forwarder, addr, data):
- for i in range(0, 10):
- forwarder.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP, addr,
- addr, data)
- self.start_listen()
- self.forwarder.connect_to_receiver()
- self.assertRaises(SocketSessionError, multi_push, self.forwarder,
- ('192.0.2.1', 53), self.large_text)
-
- def test_bad_pop(self):
- # This is a subset of C++ badPop test. We only check pop() raises
- # SocketSessionError when it internally fails to get the FD.
- # Other cases would require passing a valid FD from the test,
- # which would make the test too complicated. As a wrapper checking
- # one common failure case should be reasonably sufficient.
-
- self.start_listen()
- s = socket.socket(socket.AF_UNIX, SOCK_STREAM, 0)
- s.setblocking(False)
- s.connect(TEST_UNIX_FILE)
- accept_sock = self.accept_forwarder()
- receiver = SocketSessionReceiver(accept_sock)
- s.close()
- self.assertRaises(SocketSessionError, receiver.pop)
- accept_sock.close()
-
-class TestReceiver(unittest.TestCase):
- # We only check a couple of failure cases on construction. Valid cases
- # are covered in TestForwarder.
-
- def test_bad_init(self):
- class FakeSocket:
- # pretending to be th standard socket class, but its fileno() is
- # bogus.
- def fileno(self):
- return None
- self.assertRaises(TypeError, SocketSessionReceiver, 1)
- self.assertRaises(TypeError, SocketSessionReceiver, FakeSocket())
-
-if __name__ == '__main__':
- unittest.main()
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""Various functions for working with files and directories."""
-
-from os.path import exists, join
-
-def path_search(filename, paths):
- """
- Searches list of paths to find filename in one of them. The found one will
- be returned or IOError will be returned if it isn't found.
- """
- for p in paths:
- f = join(p, filename)
- if exists(f):
- return f
- raise IOError("'" + filename + "' not found in " + str(paths))
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-Module to manipulate the python processes.
-
-It contains only function to rename the process, which is currently
-wrapper around setproctitle library. Does not fail if the setproctitle
-module is missing, but does nothing in that case.
-"""
-try:
- from setproctitle import setproctitle
-except ImportError:
- def setproctitle(_): pass
-import sys
-import os.path
-
-"""
-Rename the current process to given name (so it can be found in ps).
-If name is None, use zero'th command line argument.
-"""
-def rename(name=None):
- if name is None:
- name = os.path.basename(sys.argv[0])
- setproctitle(name)
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import threading
-import socket
-import select
-
-SOCK_DATA = b'somedata'
-class NoPollMixIn:
- '''This is a mix-in class to override the function serve_forever()
- and shutdown() in class socketserver.BaseServer.
-
- As commented in the module source code, serve_forever() in
- socketserver.BaseServer uses polling for a shutdown request, which
- "reduces the responsiveness to a shutdown request and wastes cpu at
- all other times."
-
- This class fixes this problem by introducing internal message
- passing via a separate socket. Note, however, that according to
- the module documentation serve_forever() and shutdown() are not
- categorized as functions that can be overridden via mix-ins. So
- this mix-in class may not be compatible with future versions of
- socketserver. It should be considered a short term workaround
- until the original implementation is fixed.
-
- The NoPollMixIn class should be used together with
- socketserver.BaseServer or some derived classes of it, and it must
- be placed before the corresponding socketserver class. In
- addition, the constructor of this mix-in must be called
- explicitly in the derived class. For example, a basic TCP server
- without the problem of polling is created as follows:
-
- class MyServer(NoPollMixIn, socketserver.TCPServer):
- def __init__(...):
- ...
- NoPollMixIn.__init__(self)
- ...
-
- To shutdown the server correctly, the serve_forever() method must
- be run in a separate thread, and shutdown() must be called from
- some other thread.
- '''
- def __init__(self):
- self.__read_sock, self.__write_sock = socket.socketpair()
- self._is_shut_down = threading.Event()
-
- def serve_forever(self, poll_interval=None):
- ''' Overrides the serve_forever([poll_interval]) in class
- socketserver.BaseServer.
-
- It uses a socketpair to wake up the select when shutdown() is
- called in anther thread. Note, parameter 'poll_interval' is
- just used for interface compatibility; it's never used in this
- function.
- '''
- while True:
- # block until the self.socket or self.__read_sock is readable
- try:
- r, w, e = select.select([self, self.__read_sock], [], [])
- except select.error as err:
- if err.args[0] == EINTR:
- continue
- else:
- break
-
- if self.__read_sock in r:
- break
- else:
- self._handle_request_noblock();
-
- self._is_shut_down.set()
-
- def shutdown(self):
- '''Stops the serve_forever loop.
-
- Blocks until the loop has finished, the function should be called
- in another thread when serve_forever is running, or it will block.
- '''
- self.__write_sock.send(SOCK_DATA) # make self.__read_sock readable.
- self._is_shut_down.wait() # wait until the serve thread terminate
+++ /dev/null
-PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = process_test.py socketserver_mixin_test.py file_test.py
-PYTESTS += address_formatter_test.py traceback_handler_test.py
-EXTRA_DIST = $(PYTESTS)
-
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
-LIBRARY_PATH_PLACEHOLDER =
-if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
-endif
-
-# test using command-line arguments, so use check-local target instead of TESTS
-check-local:
-if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
- rm -f .coverage
- ${LN_S} $(abs_top_srcdir)/.coverage .coverage
-endif
- for pytest in $(PYTESTS) ; do \
- echo Running test: $$pytest ; \
- $(LIBRARY_PATH_PLACEHOLDER) \
- PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
- $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
- done
+++ /dev/null
-# Copyright (C) 2013 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import socket
-import unittest
-from isc.util.address_formatter import AddressFormatter
-
-class AddressFormatterTest(unittest.TestCase):
- def test_v4(self):
- self.assertEqual("127.0.0.1:123",
- str(AddressFormatter(("127.0.0.1", 123))))
- self.assertEqual("127.0.0.1:123",
- str(AddressFormatter(("127.0.0.1", 123), None)))
- self.assertEqual("192.0.2.1:1",
- str(AddressFormatter(("192.0.2.1", 1))))
-
- def test_v6(self):
- self.assertEqual("[::1]:123",
- str(AddressFormatter(("::1", 123))));
- self.assertEqual("[::1]:123",
- str(AddressFormatter(("::1", 123), None)))
- self.assertEqual("[2001:db8::]:1",
- str(AddressFormatter(("2001:db8::", 1))))
-
- def test_force_family_good(self):
- self.assertEqual("127.0.0.1:123",
- str(AddressFormatter(("127.0.0.1", 123),
- socket.AF_INET)))
- self.assertEqual("[::1]:123",
- str(AddressFormatter(("::1", 123),
- socket.AF_INET6)))
-
- def test_force_family_bad(self):
- """
- These results are 'bad' as in they don't return the value as
- specified by our guidelines, since the internal check is skipped if
- the family is given
- """
- self.assertEqual("[127.0.0.1]:123",
- str(AddressFormatter(("127.0.0.1", 123),
- socket.AF_INET6)))
- self.assertEqual("::1:123",
- str(AddressFormatter(("::1", 123),
- socket.AF_INET)))
-
- def test_bad_values(self):
- self.assertRaises(ValueError, str, AddressFormatter("string"))
- self.assertRaises(ValueError, str, AddressFormatter(None))
- self.assertRaises(ValueError, str, AddressFormatter(1))
- self.assertRaises(ValueError, str, AddressFormatter(("::1", 123), 1))
- self.assertRaises(ValueError, str, AddressFormatter(("::1", 123), 1))
-
-if __name__ == "__main__":
- unittest.main()
+++ /dev/null
-# Copyright (C) 2011 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import isc.util.file
-import unittest
-
-class FileTest(unittest.TestCase):
- def test_search_path_find(self):
- """Test it returns the first occurence of the file"""
- self.assertEqual('./Makefile',
- isc.util.file.path_search('Makefile',
- ['/no/such/directory/', '.',
- '../tests/']))
-
- def test_search_path_notfound(self):
- """Test it throws an exception when the file can't be found"""
- self.assertRaises(IOError, isc.util.file.path_search, 'no file', ['/no/such/directory'])
-
-if __name__ == "__main__":
- unittest.main()
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""Tests for isc.util.process."""
-import unittest
-import isc.util.process
-run_tests = True
-try:
- import setproctitle
-except ImportError:
- run_tests = False
-
-class TestRename(unittest.TestCase):
- """Testcase for isc.process.rename."""
- def __get_self_name(self):
- return setproctitle.getproctitle()
-
- @unittest.skipIf(not run_tests, "Setproctitle not installed, not testing")
- def test_rename(self):
- """Test if the renaming function works."""
- isc.util.process.rename("rename-test")
- self.assertEqual("rename-test", self.__get_self_name())
- isc.util.process.rename()
- self.assertEqual("process_test.py", self.__get_self_name())
-
-if __name__ == "__main__":
- unittest.main()
+++ /dev/null
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import unittest
-from isc.util.socketserver_mixin import NoPollMixIn
-import socketserver
-import threading
-import socket
-import time
-
-class MyHandler(socketserver.BaseRequestHandler):
- def handle(self):
- data = self.request.recv(20)
- self.request.send(data)
-
-class MyServer(NoPollMixIn,
- socketserver.ThreadingMixIn,
- socketserver.TCPServer):
-
- def __init__(self, server_addr, handler_class):
- NoPollMixIn.__init__(self)
- socketserver.TCPServer.__init__(self, server_addr, handler_class)
-
-def send_and_get_reply(ip, port, msg):
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.connect((ip, port))
- sock.send(msg)
- response = sock.recv(20)
- sock.close()
- return response
-
-class TestNoPollMixIn(unittest.TestCase):
- def test_serve_forever(self):
- # use port 0 to select an arbitrary unused port.
- server = MyServer(('127.0.0.1', 0), MyHandler)
- ip, port = server.server_address
- server_thread = threading.Thread(target=server.serve_forever)
- server_thread.start()
-
- msg = b'senddata'
- self.assertEqual(msg, send_and_get_reply(ip, port, msg))
- self.assertTrue(server_thread.is_alive())
-
- self.assertFalse(server._is_shut_down.is_set())
- server.shutdown() # Now shutdown the server
- self.assertTrue(server._is_shut_down.is_set())
-
-if __name__== "__main__":
- unittest.main()
-
-
+++ /dev/null
-# Copyright (C) 2013 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import unittest
-import os
-import isc.log
-import isc.util.traceback_handler
-
-class TracebackHandlerTest(unittest.TestCase):
- def setUp(self):
- """
- Save some things to be restored later, if we overwrite them
- for tests.
- """
- self.exit = isc.util.traceback_handler.sys.exit
- self.logger = isc.util.traceback_handler.logger
- # Sanity check - the functions exist.
- self.assertTrue(self.exit)
- self.assertTrue(self.logger)
-
- def tearDown(self):
- """
- Restore mocked things.
- """
- isc.util.traceback_handler.sys.exit = self.exit
- isc.util.traceback_handler.logger = self.logger
-
- def test_success(self):
- """
- Test the handler doesn't influence the result of successful
- function.
- """
- self.called = False
- def succ():
- self.called = True
- return 42
-
- self.assertEqual(42,
- isc.util.traceback_handler.traceback_handler(succ))
- self.assertTrue(self.called)
-
- def test_success_no_returned_value(self):
- """
- Test the handler handles the case where main() returns nothing.
- """
- self.called = False
- def succ():
- self.called = True
- return
-
- self.assertIsNone(isc.util.traceback_handler.traceback_handler(succ))
- self.assertTrue(self.called)
-
- def test_exception(self):
- """
- Test the exception is caught and logged, but not propagated.
- """
- # Mock up bunch of things
- self.exited = False
- def exit(status):
- self.assertEqual(1, status)
- self.exited = True
- isc.util.traceback_handler.sys.exit = exit
- self.logged = False
- obj = self
- class Logger:
- def fatal(self, message, ename, exception, filename):
- obj.assertTrue(isinstance(exception, Exception))
- obj.assertEqual('Exception', ename)
- obj.assertTrue(os.path.isfile(filename))
- with open(filename) as f:
- text = f.read()
- obj.assertTrue(text.startswith('Traceback'))
- os.remove(filename)
- obj.logged = True
- isc.util.traceback_handler.logger = Logger()
- # The failing function
- def fail():
- raise Exception('Anybody there?')
- # Does not raise, but returns nothing
- self.assertIsNone(isc.util.traceback_handler.traceback_handler(fail))
- # It logged and exited (sane values for those are checked in the mocks)
- self.assertTrue(self.exited)
- self.assertTrue(self.logged)
-
-if __name__ == "__main__":
- isc.log.init("bind10")
- isc.log.resetUnitTestRootLogger()
- unittest.main()
+++ /dev/null
-# Copyright (C) 2013 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-from isc.log_messages.util_messages import *
-import sys
-import tempfile
-import os
-import traceback
-
-logger = isc.log.Logger('util')
-
-def traceback_handler(main):
- """
- Handle uncaught exception from the main callable.
-
- The function runs the callable passed as main (it is called
- without any provided parameters). If it raises any exception,
- the exception is logged and the application is terminated.
- """
- try:
- return main()
- except Exception as e:
- fd, name = tempfile.mkstemp(text=True)
- with os.fdopen(fd, 'w') as handle:
- traceback.print_exc(None, handle)
- logger.fatal(PYTHON_UNHANDLED_EXCEPTION, type(e).__name__, e, name)
- sys.exit(1)
+++ /dev/null
-# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# No namespace declaration - these constants go in the global namespace
-# of the isc.util.util_messages python module.
-
-% PYTHON_UNHANDLED_EXCEPTION program terminated with exception %1: %2. More info in %3
-A program encountered an unexpected situation and terminated because it
-didn't know how to handle it. The exact problem is logged in the
-message. This might be caused by a bug in the program, a broken
-installation, or just a very rare condition which wasn't handled in the
-code. A full stack trace is left in the generated file. If you report a
-bug for this exception, please include that file. The file will not be
-deleted automatically and you may want to remove it when you no longer
-need the information there.