xenial (7) elektra-architecture.7.gz

Provided by: elektra-doc_0.8.14-5_all bug

NAME

       elektra-architecture - architecture of elektra

       In this document we start to explain the implementation of Elektra. There are several follow-up documents
       which explain all details of:

       •   error handling elektra-error-handling.md,

       •   data structures elektra-data-structures.md, and

       •   finally the core algorithm elektra-algorithm.md.

       We discuss problems and the solution space so that  the  reader  can  understand  the  rationale  of  how
       problems were solved.

       To  help  readers  to  understand  the  algorithm that glues together the plugins, we first describe some
       details  of  the  data  structures  elektra-data-structures.md.   Full   knowledge   of   the   algorithm
       elektra-algorithm.md  is  not  presumed  to  be  able  to develop most plugins (with the exception of the
       resolver /src/plugins/resolver/).

       Further important concepts are explained in:

       •   bootstrapping elektra-bootstrapping.md

       •   granularity elektra-granularity.md

       •   sync-flag elektra-sync-flag.md

API

       The aim of the Elektra Project is to design and implement a powerful  API  for  configuration.  When  the
       project started, we assumed that this goal was easy to achieve, but dealing with the semantics turned out
       to be a difficult problem. For the implementation, an ambitious  solution  is  required  because  of  the
       necessary  modularity  to  implement flexible backends as introduced in Elektra. But also the design of a
       good API has proved to be much more difficult than expected.

   Changes in the APIs
       From Elektra 0.7 to Elektra 0.8, we changed the API of Elektra  as  little  as  possible.  It  should  be
       mentioned  that  KeySet  is  now  always sorted by name. The function ksSort() is now depreciated and was
       removed. The handling of removed keys was modified. Additionally, the API for metadata has  fundamentally
       changed,  but  the  old  interface still works. These changes will be described in implementation of meta
       data elektra-meta-data.md. However, the implementation of  Elektra  changed  radically  as  discussed  in
       algorithm elektra-algorithm.md.

   API Design
       API Design presents a critical craft every programmer should be aware of. We will shortly present some of
       the main design issues that matter and show how Elektra has solved them.

       A design goal is to detect errors early. As easy as it sounds, as difficult it  is  to  actually  achieve
       this  goal.  Elektra  tries  to  avoid  the  problem by checking data being inserted into Key and KeySet.
       Elektra catches  many  errors  like  invalid  key  names  soon.  Elektra  allows  plugins  to  check  the
       configuration before it is written into the key database so that problematic values are never stored.

       ´´Hard  to  use  it  wrong´´ tends to be a more important design objective than ´´Easy to use it right´´.
       Searching for a stupid bug costs more time than falling into some standard traps which are  explained  in
       documentation.  In  Elektra,  the  data  structures are robust and some efforts were taken to make misuse
       unlikely.

       Another fundamental principle is that the  API  must  hide  implementation  details  and  should  not  be
       optimised  towards  speed. In Elektra, the actual process of making configuration permanent is completely
       hidden.

       ´´Off-by-one confusion´´ is a topic of its own. The best is to stick to the conventions  the  programming
       language  gives.  For returning sizes of strings, it must be clear whether a terminating ´\0´ is included
       or not. All such decisions must be consistent. In Elektra the terminating null is always included in  the
       size.

       The  interface  must  be  as  small as possible to tackle problems addressed by the library. Internal and
       external APIs must be separated. Internal APIs in libraries shall be declared as static  to  prevent  its
       export. In Elektra, internal names start with elektra opposed to the external names starting with key, ks
       or kdb.

       Elektra always passes user context pointers, but never passes or receives a full data structure by value.
       It  is  impossible to be ABI\footnote{We will read more about ABI in \secref{ABI}.} compatible otherwise.
       Elektra is restrictive in what it returns (strong postconditions), but as liberal as  possible  for  what
       comes  in  (preconditions are avoided where possible). In Elektra even null pointers are accepted for any
       argument.

       ´´Free everything you allocate´´ is a difficult topic in some cases. If  Elektra  cannot  free  space  or
       other  resources  after  every  call,  it provides a close() function. Everything will be freed. The tool
       Valgrind with Memcheck helps us locate problems. The whole test suite runs without any  memory  problems.
       The user is responsible for deleting all created Key and KeySet objects and closing the KDB handle.

       As  a final statement, we note that the UNIX philosophy should always be considered: ´´Do only one thing,
       but do it in the best way. Write it that way that programs work together well.´´

Modules

       Elektra´s core can be compiled with a C compiler conforming to the ISO/IEC 9899:1999 standard:

       •   One line comments,

       •   inline functions,

       •   snprintf()

       •   inttypes.h and

       •   variable declaration at any place

       are used in addition to what is already defined in the standard ISO/IEC  9899:1990,  called  C99  in  the
       following  text. Functions not conforming to C99 are considered to be not portable enough for Elektra and
       are separated into plugins. But there is one notable exception: it  must  be  the  core´s  task  to  load
       plugins.  Unfortunately,  C99  does  not  know  anything  about modules. POSIX (Portable Operating System
       Interface) provides dlopen(), but other operating systems have dissimilar APIs  for  that  purpose.  They
       sometimes  behave  differently,  use  other names for the libraries and have incompatible error reporting
       systems. Because of these requirements Elektra provides  a  small  internal  API  to  load  such  modules
       independently  from  the  operating  system.  This  API  also  hides the fact that modules must be loaded
       dynamically if they are not available statically.

       Plugins are usually realised with modules. Modules  and  libraries  are  technically  the  same  in  most
       systems.  (One exception is Mac OS X.) After the module is loaded, the special function plugin factory is
       searched for. This function returns a new plugin. With the plugin factory the actual plugins are created.

   Static loading
       For the static loading of modules, the modules must be built-in.  With  dlopen(const  char*  file)  POSIX
       provides  a  solution to look up such symbols by passing a null pointer for the parameter file. Non-POSIX
       operating systems may not support this  kind  of  static  loading.  Therefore,  Elektra  provides  a  C99
       conforming solution for that problem: a data structure stores the pointers to the plugin factory of every
       plugin. The build system generates the source file of this data structure because it depends on  built-in
       plugins as shown in Figure~\ref{fig:architecture}.

       Elektra  distinguishes  internally  between  modules and plugins. Several plugins can be created out of a
       single module. During the creation process of the plugin, dynamic information -- like  the  configuration
       or the data handle -- is added.

   API
       The API of \intro[libloader@\lstinline{libloader}]{libloader} consists of the following functions:

       Interface of Module System:

           elektraModulesInit (KeySet *modules, Key *error); elektraPluginFactory
           elektraModulesLoad (KeySet *modules,
                   const char *name, Key *error);
           int elektraModulesClose (KeySet *modules, Key *error); \end{lstlisting}

       elektraModulesInit()  initialises  the  module  cache  and calls necessary operating system facilities if
       needed. \method{elektraModulesLoad()} does the main work by either returning  a  pointer  to  the  plugin
       factory  from  cache  or loading it from the operating system. The plugin factory creates plugins that do
       not have references to the module anymore. elektraModulesClose() cleans up the cache  and  finalises  all
       connections with the operating system.

       Not  every  plugin  is  loaded  by  libloader.  For  example,  the  version plugin, which exports version
       information, is implemented internally.

Mount Point Configuration

       kdb mount creates a mount point configuration as shown in the example  below.  fstab  is  a  unique  name
       within the mount point configuration provided by the administrator.

       Example for a mount point configuration:

           system/elektra/mountpoints system/elektra/mountpoints/fstab
           system/elektra/mountpoints/fstab/config
           system/elektra/mountpoints/fstab/config/path=fstab
           system/elektra/mountpoints/fstab/config/struct=list FStab
           system/elektra/mountpoints/fstab/config/struct/FStab
           system/elektra/mountpoints/fstab/config/struct/FStab/device
           system/elektra/mountpoints/fstab/config/struct/FStab/dumpfreq
           system/elektra/mountpoints/fstab/config/struct/FStab/mpoint
           system/elektra/mountpoints/fstab/config/struct/FStab/options
           system/elektra/mountpoints/fstab/config/struct/FStab/passno
           system/elektra/mountpoints/fstab/config/struct/FStab/type
           system/elektra/mountpoints/fstab/errorplugins
           system/elektra/mountpoints/fstab/errorplugins/#5#resolver#resolver#
           system/elektra/mountpoints/fstab/getplugins
           system/elektra/mountpoints/fstab/getplugins/#0#resolver
           system/elektra/mountpoints/fstab/getplugins/#5#fstab#fstab#
           system/elektra/mountpoints/fstab/mountpoint /fstab
           system/elektra/mountpoints/fstab/setplugins
           system/elektra/mountpoints/fstab/setplugins/#0#resolver
           system/elektra/mountpoints/fstab/setplugins/#1#struct#struct#
           system/elektra/mountpoints/fstab/setplugins/#2#type#type#
           system/elektra/mountpoints/fstab/setplugins/#3#path#path#
           system/elektra/mountpoints/fstab/setplugins/#3#path#path#/config
           system/elektra/mountpoints/fstab/setplugins/#3#path#path#/config/path/allow=proc tmpfs none
           system/elektra/mountpoints/fstab/setplugins/#5#fstab
           system/elektra/mountpoints/fstab/setplugins/#7#resolver \end{lstlisting}

       Let us look at the subkeys below the key system/elektra/mountpoints/fstab:

       config Everything  below  \keyname{config}  is  the  system´s  configuration of the backend. Every plugin
              within  the  backend  will  find  this  configuration  directly  below  \keyname{system/}  in  its
              \intro{plugin configuration}. For example,

              system/elektra/mountpoints/fstab/config/struct/FStab/mpoint

       will be translated to

           system/struct/FStab/mpoint

       and inserted into the plugin configuration for all plugins in the fstab backend.

       It  is  the place where configuration can be provided for every plugin of a backend. The contract checker
       deduces this configuration to satisfy the contract for a plugin. Fstab, for example, claims in a contract
       that  it  needs  ´´struct´´.  But  the  struct  plugin needs a configuration to work properly. Fstab will
       provide this configuration. The \empha{contract checker} writes out the configuration  looking  like  the
       one in this example.

       config/path
              is  a  common setting needed by the resolver plugin. It is the relative path to a filename that is
              used by this backend. On UNIX systems, the resolver would determine the name /etc/fstab for system
              configuration.

       mountpoint
              is  a key that represents the mount point. Its value is the location where the backend is mounted.
              If a mount point has an  entry  for  both  the  user  and  the  system  hierarchy,  it  is  called
              \intro{cascading  mount  point}.  A  cascading  mount point differs from two separate mount points
              because internally only one backend is created. In the example, the mount point /fstab means  that
              the backend handles both user/fstab and system/fstab. If the mount point is /, the backend will be
              mounted to all namespaces except spec, including both user and system.

       errorplugins
              presents a list of all plugins to be executed  in  the  error  case  of  kdbSet()  which  will  be
              explained in \secref{error situation}.

       getplugins
              is  a  list  of  all  plugins  used when reading the configuration from the key database. They are
              executed in kdbGet().

       setplugins
              contains a list of all plugins used when storing configuration. They are executed in kdbSet().

       Each of the plugins inside the three lists may have the  subkey  config.  The  configuration  below  this
       subkey  provides plugin specific configuration. This configuration appears in the user´s configuration of
       the plugin. Configuration is renamed properly. For example, the key

           system/elektra/mountpoints/fstab/setplugins/#3#path#path#/config/path/allow

       is transformed to

           user/path/allow

       and appears in the plugin configuration of the path plugin inside the fstab backend.

   Referencing
       The same plugin often must occur in more than one place within a backend. The most common use case  is  a
       plugin that has to be executed for both kdbGet() and kdbSet(). It must be the same plugin if it preserves
       state between the executions.

       Other plugins additionally have to handle  error  or  success  situations.  One  example  of  exceptional
       intensive  use  is  the resolver plugin. It is executed twice in kdbSet(). In kdbGet() it is also used as
       shown in Listing~\ref{lst:mount point configuration}.

       \lstinline[language=]{#nname} introduces a new plugin from the module name  which  cannot  be  referenced
       later. The cypher n appoints the actual placement of the plugin. \lstinline[language=]{#n#name#

   Changing Mount Point Configuration
       When  the  user  changes  the  mount  point  configuration, without countermeasures, applications already
       started will continue to run with the old configuration. This could lead to a problem if backends in  use
       are  changed  or  removed.  It is necessary to restart all such programs. Notification is the best way to
       deal with the situation. Changes of the mount point configuration, however, do not occur often. For  some
       systems, the manual restart may also be appropriate.

       In  this  situation, applications can receive warning or error information if the configuration files are
       moved or removed. The most adverse situation occurs if the sequence of locking multiple files produces  a
       \empha{dead  lock}.  Under  normal  circumstances, the sequence of locking the files is deterministic, so
       either all locks can be requested or another program will be served  first.  But  several  programs  with
       different mount point configurations running at the same time can cause a disaster. The problem gets even
       worse, because kdb mount is unable to detect such situations. Every specific  mount  point  configuration
       for itself is trouble-free.

       But  still  a  dead  lock can arise when multiple programs run with different mount point configurations.
       Suppose we have a program A which uses the backends B1 and B2 that requests locks for the  files  F1  and
       F2.  Then  the  mount  point  configuration is changed. The user removes B1 and introduces B3. B3 is in a
       different path mounted after B2, but also accesses the same file F1. The program B starts after the mount
       point  configuration is changed. So it uses the backends B2 and B3. If the scheduler decides that first A
       and then B both successfully lock the files F1 and F2, a dead  lock  situation  happens  because  in  the
       afterwards the applications A and B try to lock F2 and F1.

       A  manual  solution  for  this  problem is to enable kdb to output a list of processes that still use old
       mount point configuration. The administrator can restart these processes. The preferred  solution  is  to
       use notification for mount point configuration changes or simply to use a lock-free resolver.

       Continue reading with the data structures elektra-data-structures.md.

                                                  November 2015                          ELEKTRA-ARCHITECTURE(7)