diff --git a/doc/sphinx/Pacemaker_Development/c.rst b/doc/sphinx/Pacemaker_Development/c.rst index 306b77f9a7..7a3a6bf309 100644 --- a/doc/sphinx/Pacemaker_Development/c.rst +++ b/doc/sphinx/Pacemaker_Development/c.rst @@ -1,842 +1,924 @@ .. index:: single: C pair: C; guidelines C Coding Guidelines ------------------- Pacemaker is a large project accepting contributions from developers with a wide range of skill levels and organizational affiliations, and maintained by multiple people over long periods of time. Following consistent guidelines makes reading, writing, and reviewing code easier, and helps avoid common mistakes. Some existing Pacemaker code does not follow these guidelines, for historical reasons and API backward compatibility, but new code should. Code Organization ################# Pacemaker's C code is organized as follows: +-----------------+-----------------------------------------------------------+ | Directory | Contents | +=================+===========================================================+ | daemons | the Pacemaker daemons (pacemakerd, pacemaker-based, etc.) | +-----------------+-----------------------------------------------------------+ | include | header files for library APIs | +-----------------+-----------------------------------------------------------+ | lib | libraries | +-----------------+-----------------------------------------------------------+ | tools | command-line tools | +-----------------+-----------------------------------------------------------+ Source file names should be unique across the entire project, to allow for individual tracing via ``PCMK_trace_files``. .. index:: single: C; library single: C library Pacemaker Libraries ################### +---------------+---------+---------------+---------------------------+-------------------------------------+ | Library | Symbol | Source | API Headers | Description | | | prefix | location | | | +===============+=========+===============+===========================+=====================================+ | libcib | cib | lib/cib | | include/crm/cib.h | .. index:: | | | | | | include/crm/cib/* | single: C library; libcib | | | | | | single: libcib | | | | | | | | | | | | API for pacemaker-based IPC and | | | | | | the CIB | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libcrmcluster | pcmk | lib/cluster | | include/crm/cluster.h | .. index:: | | | | | | include/crm/cluster/* | single: C library; libcrmcluster | | | | | | single: libcrmcluster | | | | | | | | | | | | Abstract interface to underlying | | | | | | cluster layer | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libcrmcommon | pcmk | lib/common | | include/crm/common/* | .. index:: | | | | | | some of include/crm/* | single: C library; libcrmcommon | | | | | | single: libcrmcommon | | | | | | | | | | | | Everything else | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libcrmservice | svc | lib/services | | include/crm/services.h | .. index:: | | | | | | single: C library; libcrmservice | | | | | | single: libcrmservice | | | | | | | | | | | | Abstract interface to supported | | | | | | resource types (OCF, LSB, etc.) | +---------------+---------+---------------+---------------------------+-------------------------------------+ | liblrmd | lrmd | lib/lrmd | | include/crm/lrmd*.h | .. index:: | | | | | | single: C library; liblrmd | | | | | | single: liblrmd | | | | | | | | | | | | API for pacemaker-execd IPC | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libpacemaker | pcmk | lib/pacemaker | | include/pacemaker*.h | .. index:: | | | | | | include/pcmki/* | single: C library; libpacemaker | | | | | | single: libpacemaker | | | | | | | | | | | | High-level APIs equivalent to | | | | | | command-line tool capabilities | | | | | | (and high-level internal APIs) | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libpe_rules | pe | lib/pengine | | include/crm/pengine/* | .. index:: | | | | | | single: C library; libpe_rules | | | | | | single: libpe_rules | | | | | | | | | | | | Scheduler functionality related | | | | | | to evaluating rules | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libpe_status | pe | lib/pengine | | include/crm/pengine/* | .. index:: | | | | | | single: C library; libpe_status | | | | | | single: libpe_status | | | | | | | | | | | | Low-level scheduler functionality | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libstonithd | stonith | lib/fencing | | include/crm/stonith-ng.h| .. index:: | | | | | | include/crm/fencing/* | single: C library; libstonithd | | | | | | single: libstonithd | | | | | | | | | | | | API for pacemaker-fenced IPC | +---------------+---------+---------------+---------------------------+-------------------------------------+ Public versus Internal APIs ___________________________ Pacemaker libraries have both internal and public APIs. Internal APIs are those used only within Pacemaker; public APIs are those offered (via header files and documentation) for external code to use. Generic functionality needed by Pacemaker itself, such as string processing or XML processing, should remain internal, while functions providing useful high-level access to Pacemaker capabilities should be public. When in doubt, keep APIs internal, because it's easier to expose a previously internal API than hide a previously public API. Internal APIs can be changed as needed. The public API/ABI should maintain a degree of stability so that external applications using it do not need to be rewritten or rebuilt frequently. Many OSes/distributions avoid breaking API/ABI compatibility within a major release, so if Pacemaker breaks compatibility, that significantly delays when OSes can package the new version. Therefore, changes to public APIs should be backward-compatible (as detailed throughout this chapter), unless we are doing a (rare) release where we specifically intend to break compatibility. External applications known to use Pacemaker's public C API include `sbd `_ and dlm_controld. .. index:: pair: C; API documentation single: Doxygen API Documentation _________________ Pacemaker uses `Doxygen `_ to automatically generate its `online API documentation `_, so all public API (header files, functions, structs, enums, etc.) should be documented with Doxygen comment blocks. Other code may be documented in the same way if desired, with an ``\internal`` tag in the Doxygen comment. Simple example of an internal function with a Doxygen comment block: .. code-block:: c /*! * \internal * \brief Return string length plus 1 * * Return the number of characters in a given string, plus one. * * \param[in] s A string (must not be NULL) * * \return The length of \p s plus 1. */ static int f(const char *s) { return strlen(s) + 1; } API Header File Naming ______________________ * Internal API headers should be named ending in ``_internal.h``, in the same location as public headers, with the exception of libpacemaker, which for historical reasons keeps internal headers in ``include/pcmki/pcmki_*.h``). * If a library needs to share symbols just within the library, header files for these should be named ending in ``_private.h`` and located in the library source directory (not ``include``). Such functions should be declared as ``G_GNUC_INTERNAL``, to aid compiler efficiency (glib defines this symbol appropriately for the compiler). Header files that are not library API are located in the same locations as other source code. .. index:: pair: C; naming API Symbol Naming _________________ Exposed API symbols (non-``static`` function names, ``struct`` and ``typedef`` names in header files, etc.) must begin with the prefix appropriate to the library (shown in the table at the beginning of this section). This reduces the chance of naming collisions when external software links against the library. The prefix is usually lowercase but may be all-caps for some defined constants and macros. Public API symbols should follow the library prefix with a single underbar (for example, ``pcmk_something``), and internal API symbols with a double underbar (for example, ``pcmk__other_thing``). File-local symbols (such as static functions) and non-library code do not require a prefix, though a unique prefix indicating an executable (controld, crm_mon, etc.) can be helpful to indicate symbols shared between multiple source files for the executable. +Public API Deprecation +______________________ + +Public APIs may not be removed in most Pacemaker releases, but they may be +deprecated. + +When a public API is deprecated, it is moved to a header whose name ends in +``compat.h``. The original header includes the compatibility header only if the +``PCMK_ALLOW_DEPRECATED`` symbol is undefined or defined to 1. This allows +external code to continue using the deprecated APIs, but internal code is +prevented from using them because the ``crm_internal.h`` header defines the +symbol to 0. + .. index:: pair: C; boilerplate pair: license; C pair: copyright; C C Boilerplate ############# Every C file should start with a short copyright and license notice: .. code-block:: c /* * Copyright the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under WITHOUT ANY WARRANTY. */ ** should follow the policy set forth in the `COPYING `_ file, generally one of "GNU General Public License version 2 or later (GPLv2+)" or "GNU Lesser General Public License version 2.1 or later (LGPLv2.1+)". Header files should additionally protect against multiple inclusion by defining a unique symbol of the form ``PCMK____H``. For example: .. code-block:: c #ifndef PCMK__MY_HEADER__H # define PCMK__MY_HEADER__H // header code here #endif // PCMK__MY_HEADER__H Public API header files should additionally declare "C" compatibility for inclusion by C++, and give a Doxygen file description. For example: .. code-block:: c #ifdef __cplusplus extern "C" { #endif /*! * \file * \brief My brief description here * \ingroup core */ // header code here #ifdef __cplusplus } #endif .. index:: pair: C; whitespace Line Formatting ############### * Indentation must be 4 spaces, no tabs. * Do not leave trailing whitespace. * Lines should be no longer than 80 characters unless limiting line length hurts readability. .. index:: pair: C; comment Comments ######## .. code-block:: c /* Single-line comments may look like this */ // ... or this /* Multi-line comments should start immediately after the comment opening. * Subsequent lines should start with an aligned asterisk. The comment * closing should be aligned and on a line by itself. */ .. index:: single: C; pointer Variables ######### * Pointers: .. code-block:: c /* (1) The asterisk goes by the variable name, not the type; * (2) Avoid leaving pointers uninitialized, to lessen the impact of * use-before-assignment bugs */ char *my_string = NULL; // Use space before asterisk and after closing parenthesis in a cast char *foo = (char *) bar; * Global variables should be avoided in libraries when possible. State information should instead be passed as function arguments (often as a structure). This is not for thread safety -- Pacemaker's use of forking ensures it will never be threaded -- but it does minimize overhead, improve readability, and avoid obscure side effects. * Time intervals are sometimes represented in Pacemaker code as user-defined text specifications (for example, "10s"), other times as an integer number of seconds or milliseconds, and still other times as a string representation of an integer number. Variables for these should be named with an indication of which is being used (for example, use ``interval_spec``, ``interval_ms``, or ``interval_ms_s`` instead of ``interval``). .. index:: pair: C; operator Operators ######### .. code-block:: c // Operators have spaces on both sides x = a; /* (1) Do not rely on operator precedence; use parentheses when mixing * operators with different priority, for readability. * (2) No space is used after an opening parenthesis or before a closing * parenthesis. */ x = a + b - (c * d); .. index:: single: C; if single: C; else single: C; while single: C; for single: C; switch Control Statements (if, else, while, for, switch) ################################################# .. code-block:: c /* * (1) The control keyword is followed by a space, a left parenthesis * without a space, the condition, a right parenthesis, a space, and the * opening bracket on the same line. * (2) Always use braces around control statement blocks, even if they only * contain one line. This makes code review diffs smaller if a line gets * added in the future, and avoids the chance of bad indenting making a * line incorrectly appear to be part of the block. * (3) The closing bracket is on a line by itself. */ if (v < 0) { return 0; } /* "else" and "else if" are on the same line with the previous ending brace * and next opening brace, separated by a space. Blank lines may be used * between blocks to help readability. */ if (v > 0) { return 0; } else if (a == 0) { return 1; } else { return 2; } /* Do not use assignments in conditions. This ensures that the developer's * intent is always clear, makes code reviews easier, and reduces the chance * of using assignment where comparison is intended. */ // Do this ... a = f(); if (a) { return 0; } // ... NOT this if (a = f()) { return 0; } /* It helps readability to use the "!" operator only in boolean * comparisons, and explicitly compare numeric values against 0, * pointers against NULL, etc. This helps remind the reader of the * type being compared. */ int i = 0; char *s = NULL; bool cond = false; if (!cond) { return 0; } if (i == 0) { return 0; } if (s == NULL) { return 0; } /* In a "switch" statement, indent "case" one level, and indent the body of * each "case" another level. */ switch (expression) { case 0: command1; break; case 1: command2; break; default: command3; break; } .. index:: single: C; struct Structures ########## Changes to structures defined in public API headers (adding or removing members, or changing member types) are generally not possible without breaking API compatibility. However, there are exceptions: * Public API structures can be designed such that they can be allocated only via API functions, not declared directly or allocated with standard memory functions using ``sizeof``. * This can be enforced simply by documentating the limitation, in which case new ``struct`` members can be added to the end of the structure without breaking compatibility. * Alternatively, the structure definition can be kept in an internal header, with only a pointer type definition kept in a public header, in which case the structure definition can be changed however needed. .. index:: single: C; enum Enumerations ############ * Enumerations should not have a ``typedef``, and do not require any naming convention beyond what applies to all exposed symbols. * New values should usually be added to the end of public API enumerations, because the compiler will define the values to 0, 1, etc., in the order given, and inserting a value in the middle would change the numerical values of all later values, breaking code compiled with the old values. However, if enum numerical values are explicitly specified rather than left to the compiler, new values can be added anywhere. * When defining constant integer values, enum should be preferred over ``#define`` or ``const`` when possible. This allows type checking without consuming memory. Flag groups ___________ Pacemaker often uses flag groups (also called bit fields or bitmasks) for a collection of boolean options (flags/bits). This is more efficient for storage and manipulation than individual booleans, but its main advantage is when used in public APIs, because using another bit in a bitmask is backward compatible, whereas adding a new function argument (or sometimes even a structure member) is not. .. code-block:: c #include /* (1) Define an enumeration to name the individual flags, for readability. * An enumeration is preferred to a series of "#define" constants * because it is typed, and logically groups the related names. * (2) Define the values using left-shifting, which is more readable and * less error-prone than hexadecimal literals (0x0001, 0x0002, 0x0004, * etc.). * (3) Using a comma after the last entry makes diffs smaller for reviewing * if a new value needs to be added or removed later. */ enum pcmk__some_bitmask_type { pcmk__some_value = (1 << 0), pcmk__other_value = (1 << 1), pcmk__another_value = (1 << 2), }; /* The flag group itself should be an unsigned type from stdint.h (not * the enum type, since it will be a mask of the enum values and not just * one of them). uint32_t is the most common, since we rarely need more than * 32 flags, but a smaller or larger type could be appropriate in some * cases. */ uint32_t flags = pcmk__some_value|pcmk__other_value; /* If the values will be used only with uint64_t, define them accordingly, * to make compilers happier. */ enum pcmk__something_else { pcmk__whatever = (UINT64_C(1) << 0), }; We have convenience functions for checking flags (see ``pcmk_any_flags_set()``, ``pcmk_all_flags_set()``, and ``pcmk_is_set()``) as well as setting and clearing them (see ``pcmk__set_flags_as()`` and ``pcmk__clear_flags_as()``, usually used via wrapper macros defined for specific flag groups). These convenience functions should be preferred to direct bitwise arithmetic, for readability and logging consistency. .. index:: pair: C; booleans pair: C; bool pair: C; gboolean Booleans ######## Boolean Types _____________ Booleans in C can be represented by an integer type, ``bool``, or ``gboolean``. Integers are sometimes useful for storing booleans when they must be converted to and from a string, such as an XML attribute value (for which ``crm_element_value_int()`` can be used). Integer booleans use 0 for false and nonzero (usually 1) for true. ``gboolean`` should be used with glib APIs that specify it. ``gboolean`` should always be used with glib's ``TRUE`` and ``FALSE`` constants. Otherwise, ``bool`` should be preferred. ``bool`` should be used with the ``true`` and ``false`` constants from the ``stdbool.h`` header. Testing Booleans ________________ Do not use equality operators when testing booleans. For example: .. code-block:: c // Do this if (bool1) { fn(); } if (!bool2) { fn2(); } // Not this if (bool1 == true) { fn(); } if (bool2 == false) { fn2(); } // Otherwise there's no logical end ... if ((bool1 == false) == true) { fn(); } Conversely, equality operators *should* be used with non-boolean variables, even when just testing zero or nonzero: .. code-block:: c int var1 = fn(); // Prefer this, because it gives a hint to the type when reading it if (var1 == 0) { fn2(); } // Not this, because a reader could mistakenly assume it is a boolean if (!var1) { fn2(); } .. index:: pair: C; function Functions ######### Function names should be unique across the entire project, to allow for individual tracing via ``PCMK_trace_functions``, and make it easier to search code and follow detail logs. Function Definitions ____________________ .. code-block:: c /* * (1) The return type goes on its own line * (2) The opening brace goes by itself on a line * (3) Use "const" with pointer arguments whenever appropriate, to allow the * function to be used by more callers. */ int my_func1(const char *s) { return 0; } /* Functions with no arguments must explicitly list them as void, * for compatibility with strict compilers */ int my_func2(void) { return 0; } /* * (1) For functions with enough arguments that they must break to the next * line, align arguments with the first argument. * (2) When a function argument is a function itself, use the pointer form. * (3) Declare functions and file-global variables as ``static`` whenever * appropriate. This gains a slight efficiency in shared libraries, and * helps the reader know that it is not used outside the one file. */ static int my_func3(int bar, const char *a, const char *b, const char *c, void (*callback)()) { return 0; } Return Values _____________ Functions that need to indicate success or failure should follow one of the following guidelines. More details, including functions for using them in user messages and converting from one to another, can be found in ``include/crm/common/results.h``. * A **standard Pacemaker return code** is one of the ``pcmk_rc_*`` enum values or a system errno code, as an ``int``. * ``crm_exit_t`` (the ``CRM_EX_*`` enum values) is a system-independent code suitable for the exit status of a process, or for interchange between nodes. * Other special-purpose status codes exist, such as ``enum ocf_exitcode`` for the possible exit statuses of OCF resource agents (along with some Pacemaker-specific extensions). It is usually obvious when the context calls for such. * Some older Pacemaker APIs use the now-deprecated "legacy" return values of ``pcmk_ok`` or the positive or negative value of one of the ``pcmk_err_*`` constants or system errno codes. * Functions registered with external libraries (as callbacks for example) should use the appropriate signature defined by those libraries, rather than follow Pacemaker guidelines. Of course, functions may have return values that aren't success/failure indicators, such as a pointer, integer count, or bool. Public API Functions ____________________ Unless we are doing a (rare) release where we break public API compatibility, new public API functions can be added, but existing function signatures (return type, name, and argument types) should not be changed. To work around this, an existing function can become a wrapper for a new function. .. index:: pair: C; memory Memory Management ################# * Always use ``calloc()`` rather than ``malloc()``. It has no additional cost on modern operating systems, and reduces the severity and security risks of uninitialized memory usage bugs. * Ensure that all dynamically allocated memory is freed when no longer needed, and not used after it is freed. This can be challenging in the more event-driven, callback-oriented sections of code. * Free dynamically allocated memory using the free function corresponding to how it was allocated. For example, use ``free()`` with ``calloc()``, and ``g_free()`` with most glib functions that allocate objects. .. index:: pair: C; logging + pair: C; output -Logging -####### +Logging and Output +################## + +Logging Vs. Output +__________________ + +Log messages and output messages are logically similar but distinct. +Oversimplifying a bit, daemons log, and tools output. + +Log messages are intended to help with troubleshooting and debugging. +They may have a high level of technical detail, and are usually filtered by +severity -- for example, the system log by default gets messages of notice +level and higher. + +Output is intended to let the user know what a tool is doing, and is generally +terser and less technical, and may even be parsed by scripts. Output might have +"verbose" and "quiet" modes, but it is not filtered by severity. + +Common Guidelines for All Messages +__________________________________ * When format strings are used for derived data types whose implementation may vary across platforms (``pid_t``, ``time_t``, etc.), the safest approach is to use ``%lld`` in the format string, and cast the value to ``long long``. -* Do *not* pass ``NULL`` as an argument to satisfy the ``%s`` format specifier - in logging (and more generally, ``printf``-style) functions. When the string - "" is a sufficient output representation in such case, you can use the - ``crm_str()`` convenience macro; otherwise, the ternary operator is an - obvious choice. - * Do not rely on ``%s`` handling ``NULL`` values properly. While the standard library functions might, not all Pacemaker API using them does, and it's safest to get in the habit of always ensuring format values are non-NULL. + For debug and trace messages, the ``crm_str()`` macro is sufficient and will + map NULL to the string "", but for other messages an understandable + string appropriate to the context should be used when the value is NULL. + +* The convenience macros ``pcmk__plural_s()`` and ``pcmk__plural_alt()`` are + handy when logging a word that may be singular or plural. + +Logging +_______ + +Pacemaker uses libqb for logging, but wraps it with a higher level of +functionality (see ``include/crm/common/logging*h``). + +A few macros ``crm_err()``, ``crm_warn()``, etc. do most of the heavy lifting. + +By default, Pacemaker sends logs at notice level and higher to the system log, +and logs at info level and higher to the detail log (typically +``/var/log/pacemaker/pacemaker.log``). The intent is that most users will only +ever need the system log, but for deeper troubleshooting and developer +debugging, the detail log may be helpful, at the cost of being more technical +and difficult to follow. + +The same message can have more detail in the detail log than in the system log, +using libqb's "extended logging" feature: + +.. code-block:: c + + /* The following will log a simple message in the system log, like: + + warning: Action failed: Node not found + + with extra detail in the detail log, like: + + warning: Action failed: Node not found | rc=-1005 id=hgjjg-51006 + */ + crm_warn("Action failed: %s " CRM_XS " rc=%d id=%s", + pcmk_rc_str(rc), rc, id); + + +Output +______ + +Pacemaker has a somewhat complicated system for tool output. The main benefit +is that the user can select the output format with the ``--output-as`` option +(usually "text" for human-friendly output or "xml" for reliably script-parsable +output, though ``crm_mon`` additionally supports "console" and "html"). + +A custom message can be defined with a unique string identifier, plus +implementation functions for each supported format. The caller invokes the +message using the identifier. The user selects the output format via +``--output-as``, and the output code automatically calls the appropriate +implementation function. + +The interface (most importantly ``pcmk__output_t``) is declared in +``include/crm/common/output*h``. See the API comments and existing tools for +examples. .. index:: pair: C; strings String Handling ############### Define Constants for Magic Strings __________________________________ A "magic" string is one used for control purposes rather than human reading, and which must be exactly the same every time it is used. Examples would be configuration option names, XML attribute names, or environment variable names. These should always be defined constants, rather than using the string literal everywhere. If someone mistypes a defined constant, the code won't compile, but if they mistype a literal, it could go unnoticed until a user runs into a problem. Library Functions _________________ Pacemaker's libcrmcommon has a large number of functions to assist in string handling. The most commonly used ones are: * ``pcmk__str_eq()`` tests string equality (similar to ``strcmp()``), but can handle NULL, and takes options for case-insensitive, whether NULL should be considered a match, etc. * ``crm_strdup_printf()`` takes ``printf()``-style arguments and creates a string from them (dynamically allocated, so it must be freed with ``free()``). It asserts on memory failure, so the return value is always non-NULL. String handling functions should almost always be internal API, since Pacemaker isn't intended to be used as a general-purpose library. Most are declared in ``include/crm/common/strings_internal.h``. ``util.h`` has some older ones that are public API (for now, but will eventually be made internal). char*, gchar*, and GString __________________________ When using dynamically allocated strings, be careful to always use the appropriate free function. * ``char*`` strings allocated with something like ``calloc()`` must be freed with ``free()``. Most Pacemaker library functions that allocate strings use this implementation. * glib functions often use ``gchar*`` instead, which must be freed with ``g_free()``. * Occasionally, it's convenient to use glib's flexible ``GString*`` type, which must be freed with ``g_string_free()``. .. index:: pair: C; regular expression Regular Expressions ################### - Use ``REG_NOSUB`` with ``regcomp()`` whenever possible, for efficiency. - Be sure to use ``regfree()`` appropriately. .. index:: single: Makefile.am Makefiles ######### Pacemaker uses `automake `_ for building, so the Makefile.am in each directory should be edited rather than Makefile.in or Makefile, which are automatically generated. * Public API headers are installed (by adding them to a ``HEADERS`` variable in ``Makefile.am``), but internal API headers are not (by adding them to ``noinst_HEADERS``). .. index:: pair: C; vim settings vim Settings ############ Developers who use ``vim`` to edit source code can add the following settings to their ``~/.vimrc`` file to follow Pacemaker C coding guidelines: .. code-block:: none " follow Pacemaker coding guidelines when editing C source code files filetype plugin indent on au FileType c setlocal expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80 autocmd BufNewFile,BufRead *.h set filetype=c let c_space_errors = 1 diff --git a/doc/sphinx/Pacemaker_Development/hacking.rst b/doc/sphinx/Pacemaker_Development/hacking.rst deleted file mode 100644 index 4a0e0870fd..0000000000 --- a/doc/sphinx/Pacemaker_Development/hacking.rst +++ /dev/null @@ -1,69 +0,0 @@ -Advanced Hacking on the Project -------------------------------- - -Foreword -######## - -This chapter aims to be a gentle introduction (or perhaps, rather a -summarization of advanced techniques we developed for backreferences) to how -deal with the Pacemaker internals effectively. For instance, how to: - -* debug with an ease -* verify various interesting interaction-based properties - -or simply put, all that is in the interest of the core contributors on the -project to know, master, and (preferably) also evolve -- way beyond what is in -the presumed repertoire of a generic contributor role, which is detailed in -other chapters of this guide. - -Therefore, if you think you will not benefit from any such details -in the scope of this chapter, feel free to skip it. - - -Debugging -######### - -In the GNU userland tradition, preferred way of debugging is based on ``gdb`` -(directly or via specific frontends atop) that is widely available on platforms -(semi-)supported with Pacemaker itself. - -To make some advanced debugging easier, we maintain a script defining some -useful helpers in ``devel/gdbhelpers`` file, which you can make available -in the debugging session easily when invoking it as -``gdb -x ...``. - -From within the debugger, you can then invoke the new ``pcmk`` command that -will guide you regarding other helper functions available, so we won't -replicate that here. - - -Working with mocked daemons -########################### - -Since the Pacemaker run-time consists of multiple co-operating daemons -as detailed elsewhere, tracking down the interaction details amongst -them can be rather cumbersome. Since rebuilding existing daemons in -a more modular way as opposed to clusters of mutually dependent -functions, we elected to grow separate bare-bones counterparts built -evolutionary as skeletons just to get the basic (long-term stabilized) -communication with typical daemon clients going, and to add new modules -in their outer circles (plus minimalistic hook support at those cores) -on a demand-driven basis. - -The code for these is located at ``maint/mocked``; for instance, -``based-notifyfenced.c`` module of ``based.c`` skeleton mocking -``pacemaker-based`` daemon was exactly to fulfill investigation helper -role (the case at hand was also an impulse to kick off this very -sort of maintenance support material, to begin with). - -Non-trivial knowledge of Pacemaker internals and other skills are -needed to use such devised helpers, but given the other way around, -some sorts of investigation may be even heftier, it may be the least -effort choice. And when that's the case, advanced contributors are -expected to contribute their own extensions they used to validate -the reproducibility/actual correctness of the fix along the actual -code modifications. This way, the rest of the development teams is -not required to deal with elaborate preconditions, be at guess, or -even forced to use a blind faith regarding the causes, consequences -and validity regarding the raised issues/fixes, for the greater -benefit of all. diff --git a/doc/sphinx/Pacemaker_Development/helpers.rst b/doc/sphinx/Pacemaker_Development/helpers.rst index 5b3dd8aed4..3cb8edd8ab 100644 --- a/doc/sphinx/Pacemaker_Development/helpers.rst +++ b/doc/sphinx/Pacemaker_Development/helpers.rst @@ -1,397 +1,410 @@ C Development Helpers --------------------- .. index:: single: unit testing Refactoring ########### Pacemaker uses an optional tool called `coccinelle `_ to do automatic refactoring. coccinelle is a very complicated tool that can be difficult to understand, and the existing documentation makes it pretty tough to get started. Much of the documentation is either aimed at kernel developers or takes the form of grammars. However, it can apply very complex transformations across an entire source tree. This is useful for tasks like code refactoring, changing APIs (number or type of arguments, etc.), catching functions that should not be called, and changing existing patterns. coccinelle is driven by input scripts called `semantic patches `_ written in its own language. These scripts bear a passing resemblance to source code patches and tell coccinelle how to match and modify a piece of source code. They are stored in ``devel/coccinelle`` and each script either contains a single source transformation or several related transformations. In general, we try to keep these as simple as possible. In Pacemaker development, we use a couple targets in ``devel/Makefile.am`` to control coccinelle. The ``cocci`` target tries to apply each script to every Pacemaker source file, printing out any changes it would make to the console. The ``cocci-inplace`` target does the same but also makes those changes to the source files. A variety of warnings might also be printed. If you aren't working on a new script, these can usually be ignored. If you are working on a new coccinelle script, it can be useful (and faster) to skip everything else and only run the new script. The ``COCCI_FILES`` variable can be used for this: .. code-block:: none $ make -C devel COCCI_FILES=coccinelle/new-file.cocci cocci This variable is also used for preventing some coccinelle scripts in the Pacemaker source tree from running. Some scripts are disabled because they are not currently fully working or because they are there as templates. When adding a new script, remember to add it to this variable if it should always be run. One complication when writing coccinelle scripts is that certain Pacemaker source files may not use private functions (those whose name starts with ``pcmk__``). Handling this requires work in both the Makefile and in the coccinelle scripts. The Makefile deals with this by maintaining two lists of source files: those that may use private functions and those that may not. For those that may, a special argument (``-D internal``) is added to the coccinelle command line. This creates a virtual dependency named ``internal``. In the coccinelle scripts, those transformations that modify source code to use a private function also have a dependency on ``internal``. If that dependency was given on the command line, the transformation will be run. Otherwise, it will be skipped. This means that not all instances of an older style of code will be changed after running a given transformation. Some developer intervention is still necessary to know whether a source code block should have been changed or not. Probably the easiest way to learn how to use coccinelle is by following other people's scripts. In addition to the ones in the Pacemaker source directory, there's several others on the `coccinelle website `_. Unit Testing ############ Where possible, changes to the C side of Pacemaker should be accompanied by unit tests. Much of Pacemaker cannot effectively be unit tested (and there are other testing systems used for those parts), but the ``lib`` subdirectory is pretty easy to write tests for. Pacemaker uses the `cmocka unit testing framework `_ which looks a lot like other unit testing frameworks for C and should be fairly familiar. In addition to regular unit tests, cmocka also gives us the ability to use `mock functions `_ for unit testing functions that would otherwise be difficult to test. Organization ____________ Pay close attention to the organization and naming of test cases to ensure the unit tests continue to work as they should. Tests are spread throughout the source tree, alongside the source code they test. For instance, all the tests for the source code in ``lib/common/`` are in the ``lib/common/tests`` directory. If there is no ``tests`` subdirectory, there are no tests for that library yet. Under that directory, there is a ``Makefile.am`` and additional subdirectories. Each subdirectory contains the tests for a single library source file. For instance, all the tests for ``lib/common/strings.c`` are in the ``lib/common/tests/strings`` directory. Note that the test subdirectory does not have a ``.c`` suffix. If there is no test subdirectory, there are no tests for that file yet. Finally, under that directory, there is a ``Makefile.am`` and then various source files. Each of these source files tests the single function that it is named after. For instance, ``lib/common/tests/strings/pcmk__btoa_test.c`` tests the ``pcmk__btoa_test()`` function in ``lib/common/strings.c``. If there is no test source file, there are no tests for that function yet. The ``_test`` suffix on the test source file is important. All tests have this suffix, which means all the compiled test cases will also end with this suffix. That lets us ignore all the compiled tests with a single line in ``.gitignore``: .. code-block:: none /lib/*/tests/*/*_test Adding a test _____________ Testing a new function in an already testable source file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Follow these steps if you want to test a function in a source file where there are already other tested functions. For the purposes of this example, we will add a test for the ``pcmk__scan_port()`` function in ``lib/common/strings.c``. As you can see, there are already tests for other functions in this same file in the ``lib/common/tests/strings`` directory. * cd into ``lib/common/tests/strings`` * Add the new file to the the ``check_PROGRAMS`` variable in ``Makefile.am``, making it something like this: .. code-block:: none check_PROGRAMS = \ pcmk__add_word_test \ pcmk__btoa_test \ pcmk__scan_port_test * Create a new ``pcmk__scan_port_test.c`` file, copying the copyright and include boilerplate from another file in the same directory. * Continue with the steps in `Writing the test`_. Testing a function in a source file without tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Follow these steps if you want to test a function in a source file where there are not already other tested functions, but there are tests for other files in the same library. For the purposes of this example, we will add a test for the ``pcmk_acl_required()`` function in ``lib/common/acls.c``. At the time of this documentation being written, no tests existed for that source file, so there is no ``lib/common/tests/acls`` directory. * Add to ``AC_CONFIG_FILES`` in the top-level ``configure.ac`` file so the build process knows to use directory we're about to create. That variable would now look something like: .. code-block:: none dnl Other files we output AC_CONFIG_FILES(Makefile \ ... lib/common/tests/Makefile \ lib/common/tests/acls/Makefile \ lib/common/tests/agents/Makefile \ ... ) * cd into ``lib/common/tests`` * Add to the ``SUBDIRS`` variable in ``Makefile.am``, making it something like: .. code-block:: none SUBDIRS = agents acls cmdline flags operations strings utils xpath results * Create a new ``acls`` directory, copying the ``Makefile.am`` from some other directory. * cd into ``acls`` * Get rid of any existing values for ``check_PROGRAMS`` and set it to ``pcmk_acl_required_test`` like so: .. code-block:: none check_PROGRAMS = pcmk_acl_required_test * Follow the steps in `Testing a new function in an already testable source file`_ to create the new ``pcmk_acl_required_test.c`` file. Testing a function in a library without tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Adding a test case for a function in a library that doesn't have any test cases to begin with is only slightly more complicated. In general, the steps are the same as for the previous section, except with an additional layer of directory creation. For the purposes of this example, we will add a test case for the ``lrmd_send_resource_alert()`` function in ``lib/lrmd/lrmd_alerts.c``. Note that this may not be a very good function or even library to write actual unit tests for. * Add to ``AC_CONFIG_FILES`` in the top-level ``configure.ac`` file so the build process knows to use directory we're about to create. That variable would now look something like: .. code-block:: none dnl Other files we output AC_CONFIG_FILES(Makefile \ ... lib/lrmd/Makefile \ lib/lrmd/tests/Makefile \ lib/services/Makefile \ ... ) * cd into ``lib/lrmd`` * Create a ``SUBDIRS`` variable in ``Makefile.am`` if it doesn't already exist. Most libraries should not have this variable already. .. code-block:: none SUBDIRS = tests * Create a new ``tests`` directory and add a ``Makefile.am`` with the following contents: .. code-block:: none SUBDIRS = lrmd_alerts * Follow the steps in `Testing a function in a source file without tests`_ to create the rest of the new directory structure. * Follow the steps in `Testing a new function in an already testable source file`_ to create the new ``lrmd_send_resource_alert_test.c`` file. Adding to an existing test case ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If all you need to do is add additional test cases to an existing file, none of the above work is necessary. All you need to do is find the test source file with the name matching your function and add to it and then follow the instructions in `Writing the test`_. Writing the test ________________ A test case file contains a fair amount of boilerplate. For this reason, it's usually easiest to just copy an existing file and adapt it to your needs. However, here's the basic structure: .. code-block:: c /* * Copyright 2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include /* Put your test-specific includes here */ /* Put your test functions here */ int main(int argc, char **argv) { /* Register your test functions here */ cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); } Each test-specific function should test one aspect of the library function, though it can include many assertions if there are many ways of testing that one aspect. For instance, there might be multiple ways of testing regular expression matching: .. code-block:: c static void regex(void **state) { const char *s1 = "abcd"; const char *s2 = "ABCD"; assert_int_equal(pcmk__strcmp(NULL, "a..d", pcmk__str_regex), 1); assert_int_equal(pcmk__strcmp(s1, NULL, pcmk__str_regex), 1); assert_int_equal(pcmk__strcmp(s1, "a..d", pcmk__str_regex), 0); } Each test-specific function must also be registered or it will not be called. This is done with ``cmocka_unit_test()`` in the ``main`` function: .. code-block:: c const struct CMUnitTest tests[] = { cmocka_unit_test(regex), }; Running _______ If you had to create any new files or directories, you will first need to run ``./configure`` from the top level of the source directory. This will regenerate the Makefiles throughout the tree. If you skip this step, your changes will be skipped and you'll be left wondering why the output doesn't match what you expected. To run the tests, simply run ``make check`` after previously building the source with ``make``. The test cases in each directory will be built and then run. This should not take long. If all the tests succeed, you will be back at the prompt. Scrolling back through the history, you should see lines like the following: .. code-block:: none PASS: pcmk__strcmp_test 1 - same_pointer PASS: pcmk__strcmp_test 2 - one_is_null PASS: pcmk__strcmp_test 3 - case_matters PASS: pcmk__strcmp_test 4 - case_insensitive PASS: pcmk__strcmp_test 5 - regex ============================================================================ Testsuite summary for pacemaker 2.1.0 ============================================================================ # TOTAL: 33 # PASS: 33 # SKIP: 0 # XFAIL: 0 # FAIL: 0 # XPASS: 0 # ERROR: 0 ============================================================================ make[7]: Leaving directory '/home/clumens/src/pacemaker/lib/common/tests/strings' The testing process will quit on the first failed test, and you will see lines like these: .. code-block:: none PASS: pcmk__scan_double_test 3 - trailing_chars FAIL: pcmk__scan_double_test 4 - typical_case PASS: pcmk__scan_double_test 5 - double_overflow PASS: pcmk__scan_double_test 6 - double_underflow ERROR: pcmk__scan_double_test - exited with status 1 PASS: pcmk__starts_with_test 1 - bad_input ============================================================================ Testsuite summary for pacemaker 2.1.0 ============================================================================ # TOTAL: 56 # PASS: 54 # SKIP: 0 # XFAIL: 0 # FAIL: 1 # XPASS: 0 # ERROR: 1 ============================================================================ See lib/common/tests/strings/test-suite.log Please report to users@clusterlabs.org ============================================================================ make[7]: *** [Makefile:1218: test-suite.log] Error 1 make[7]: Leaving directory '/home/clumens/src/pacemaker/lib/common/tests/strings' The failure is in ``lib/common/tests/strings/test-suite.log``: .. code-block:: none ERROR: pcmk__scan_double_test ============================= 1..6 ok 1 - empty_input_string PASS: pcmk__scan_double_test 1 - empty_input_string ok 2 - bad_input_string PASS: pcmk__scan_double_test 2 - bad_input_string ok 3 - trailing_chars PASS: pcmk__scan_double_test 3 - trailing_chars not ok 4 - typical_case FAIL: pcmk__scan_double_test 4 - typical_case # 0.000000 != 3.000000 # pcmk__scan_double_test.c:80: error: Failure! ok 5 - double_overflow PASS: pcmk__scan_double_test 5 - double_overflow ok 6 - double_underflow PASS: pcmk__scan_double_test 6 - double_underflow # not ok - tests ERROR: pcmk__scan_double_test - exited with status 1 At this point, you need to determine whether your test case is incorrect or whether the code being tested is incorrect. Fix whichever is wrong and continue. + + +Debugging +######### + +gdb +___ + +If you use ``gdb`` for debugging, some helper functions are defined in +``devel/gdbhelpers``, which can be given to ``gdb`` using the ``-x`` option. + +From within the debugger, you can then invoke the ``pcmk`` command that +will describe the helper functions available. diff --git a/doc/sphinx/Pacemaker_Development/index.rst b/doc/sphinx/Pacemaker_Development/index.rst index 19ab926b09..cbe149945e 100644 --- a/doc/sphinx/Pacemaker_Development/index.rst +++ b/doc/sphinx/Pacemaker_Development/index.rst @@ -1,34 +1,33 @@ Pacemaker Development ===================== *Working with the Pacemaker Code Base* Abstract -------- This document has guidelines and tips for developers interested in editing Pacemaker source code and submitting changes for inclusion in the project. Start with the FAQ; the rest is optional detail. Table of Contents ----------------- .. toctree:: :maxdepth: 3 :numbered: faq general python c components helpers evolution - hacking Index ----- * :ref:`genindex` * :ref:`search` diff --git a/maint/mocked/Makefile b/maint/mocked/Makefile deleted file mode 100644 index 1b729c9d52..0000000000 --- a/maint/mocked/Makefile +++ /dev/null @@ -1,44 +0,0 @@ -# -# Copyright 2019 the Pacemaker project contributors -# -# The version control history for this file may have further details. -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. This file is offered as-is, -# without any warranty. -# - -#BASED_LDFLAGS = $$(pkgconf -libs glib-2.0) \ -# $$(pkgconf -libs libxml-2.0) \ -# $$(pkgconf -libs libqb) \ -# $$(pkgconf -libs pacemaker) -BASED_LDFLAGS = $$(pkgconf -libs glib-2.0) \ - $$(pkgconf -libs libxml-2.0) \ - $$(pkgconf -libs libqb) \ - -Wl,-rpath=$(CURDIR)/../../lib/common/.libs \ - -L../../lib/common/.libs -lcrmcommon \ - -L../../lib/pacemaker/.libs -lpacemaker - -BASED_CPPFLAGS = $$(pkgconf -cflags glib-2.0) \ - $$(pkgconf -cflags libxml-2.0) \ - $$(pkgconf -cflags libqb) \ - -I ../.. -I ../../include -g - -PROGRAMS = based - -BASED_OBJECTS = based.o - -# include or not the modules as you wish -BASED_OBJECTS += based-notifyfenced.o - -all: ${PROGRAMS} - -based: $(BASED_OBJECTS) - $(CC) $(BASED_LDFLAGS) $^ -o $@ - -$(BASED_OBJECTS): %.o: %.c - $(CC) $(BASED_CPPFLAGS) $(BASED_LDFLAGS) -c $< -o $@ - -clean: - rm ${PROGRAMS} $(BASED_OBJECTS) diff --git a/maint/mocked/based-notifyfenced.c b/maint/mocked/based-notifyfenced.c deleted file mode 100644 index 1f5b714708..0000000000 --- a/maint/mocked/based-notifyfenced.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2019-2020 the Pacemaker project contributors - * - * The version control history for this file may have further details. - * - * Licensed under the GNU General Public License version 2 or later (GPLv2+). - */ - -/* - * Intended demo use case: - * - * - as root, start corosync - * - start "./based -N"; hint: - * su -s /bin/sh -c './based -N' hacluster - * - start pacemaker-fenced; hint: - * su -c 'env PCMK_logpriority=crit ../../daemons/fenced/pacemaker-fenced' - * - wait a bit (5 < seconds < 20) - * - as haclient group (or root), run "stonith admin --list-registered" - * - observe whether such invocation is blocked or not - */ - - -#include /* printf, perror */ - -#include "crm/cib.h" /* cib_zero_copy */ -#include "crm/cib/internal.h" /* CIB_OP_CREATE */ -#include "crm/msg_xml.h" /* F_SUBTYPE */ -#include "daemons/based/pacemaker-based.h" /* cib_notify_diff */ - -#include "based.h" - - -#define OPTCHAR 'N' -static size_t module_handle; - - -struct cib_notification_s { - xmlNode *msg; - struct iovec *iov; - int32_t iov_size; -}; - -/* see based/based_notify.c:cib_notify_send_one */ -static bool -mock_based_cib_notify_send_one(pcmk__client_t *client, xmlNode *xml) -{ - const char *type = NULL; - bool do_send = false; - struct iovec *iov; - ssize_t bytes; - struct cib_notification_s update = { - .msg = xml, - }; - - CRM_CHECK(client != NULL, return true); - pcmk__ipc_prepare_iov(0, xml, 0, &iov, &bytes); - update.iov = iov; - update.iov_size = bytes; - if (client->ipcs == NULL && client->remote == NULL) { - crm_warn("Skipping client with NULL channel"); - return FALSE; - } - - type = crm_element_value(update.msg, F_SUBTYPE); - CRM_LOG_ASSERT(type != NULL); - if (pcmk_is_set(client->options, cib_notify_diff) - && pcmk__str_eq(type, T_CIB_DIFF_NOTIFY, pcmk__str_casei)) { - - if (pcmk__ipc_send_iov(client, update.iov, - crm_ipc_server_event) != pcmk_rc_ok) { - crm_warn("Notification of client %s/%s failed", client->name, client->id); - } - - } - pcmk_free_ipc_event(iov); - - return FALSE; -} - -/* see based/based_notify.c:do_cib_notify + cib_notify_send */ -void -do_cib_notify(pcmk__client_t *cib_client, int options, const char *op, - xmlNode *update, int result, xmlNode *result_data, - const char *msg_type) -{ - xmlNode *update_msg = NULL; - const char *id = NULL; - - update_msg = create_xml_node(NULL, "notify"); - - - crm_xml_add(update_msg, F_TYPE, T_CIB_NOTIFY); - crm_xml_add(update_msg, F_SUBTYPE, msg_type); - crm_xml_add(update_msg, F_CIB_OPERATION, op); - crm_xml_add_int(update_msg, F_CIB_RC, result); - - if (result_data != NULL) { - id = crm_element_value(result_data, XML_ATTR_ID); - if (id != NULL) - crm_xml_add(update_msg, F_CIB_OBJID, id); - } - - if (update != NULL) { - crm_trace("Setting type to update->name: %s", crm_element_name(update)); - crm_xml_add(update_msg, F_CIB_OBJTYPE, crm_element_name(update)); - - } else if (result_data != NULL) { - crm_trace("Setting type to new_obj->name: %s", crm_element_name(result_data)); - crm_xml_add(update_msg, F_CIB_OBJTYPE, crm_element_name(result_data)); - - } else { - crm_trace("Not Setting type"); - } - -#if 0 - attach_cib_generation(update_msg, "cib_generation", the_cib); -#endif - - if (update != NULL) { - add_message_xml(update_msg, F_CIB_UPDATE, update); - } - if (result_data != NULL) { - add_message_xml(update_msg, F_CIB_UPDATE_RESULT, result_data); - } - - mock_based_cib_notify_send_one(cib_client, update_msg); - free_xml(update_msg); -} - -static gboolean -mock_based_notifyfencedmer_callback_worker(gpointer data) -{ - pcmk__client_t *cib_client = (pcmk__client_t *) data; - - xmlNode *result_data; - xmlNode *input, *update; - int options; - char update_str[4096]; - - cib__set_call_options(options, crm_system_name, cib_zero_copy); - - - input = create_xml_node(NULL, "cib"); - - /* spam it */ -#if 0 - for (size_t i = 0; i < SIZE_MAX - 1; i++) { -#else - for (size_t i = 0; i < 10000; i++) { -#endif - /* NOTE: we need to trigger fenced attention, add new fence device */ - snprintf(update_str, sizeof(update_str), -"\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"\n", i, i+1); - update = xmlReadMemory(update_str, sizeof(update_str), - "file:///tmp/update", NULL, 0)->children; - do_cib_notify(cib_client, options, CIB_OP_CREATE, input, pcmk_ok, - update, T_CIB_DIFF_NOTIFY); - free_xml(update); - }; - - free_xml(input); -} - -static void -mock_based_notifyfenced_cib_notify_hook(pcmk__client_t *cib_client) -{ - - /* MOCK: client asked for upcoming diff's, let's - spam it a bit after a while... */ - crm_info("Going to spam %s (%s) in 5 seconds...", - cib_client->name, cib_client->id); - mainloop_timer_start(mainloop_timer_add("spammer", 5000, FALSE, - mock_based_notifyfencedmer_callback_worker, - cib_client)); -} - -/* * */ - -static int -mock_based_notifyfenced_argparse_hook(struct mock_based_context_s *ctxt, - bool usage, int argc_to_go, - const char *argv_to_go[]) -{ - const char *opt = *argv_to_go; -restart: - switch(*opt) { - case '-': - if (opt == *argv_to_go) { - opt++; - goto restart; - } - break; - case OPTCHAR: - if (usage) { - printf("spam the \"cib diff\" notification client" - " (targeting pacemaker-fenced in particular)\n"); - - } else { -#if 0 - ctxt->modules[module_handle]->priv = - malloc(sizeof(mock_based_notifyfenced_priv_t)); - if (ctxt->modules[module_handle]->priv == NULL) { - perror("malloc"); - return -1; - } -#endif - } - return 1; - } - return 0; -} - -#if 0 -static void -mock_based_notifyfenced_destroy_hook(module_t *mod) { - free(mod->priv); -} -#endif - -__attribute__((__constructor__)) -void -mock_based_notifyfenced_init(void) { - module_handle = mock_based_register_module((module_t){ - .shortopt = OPTCHAR, - .hooks = { - .argparse = mock_based_notifyfenced_argparse_hook, - //.destroy = mock_based_notifyfenced_destroy_hook, - /* specialized hooks */ - .cib_notify = mock_based_notifyfenced_cib_notify_hook, - } - }); -} diff --git a/maint/mocked/based.c b/maint/mocked/based.c deleted file mode 100644 index 63f61a63ac..0000000000 --- a/maint/mocked/based.c +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Copyright 2019-2020 the Pacemaker project contributors - * - * The version control history for this file may have further details. - * - * Licensed under the GNU General Public License version 2 or later (GPLv2+). - */ - -/* - * Clean room attempt (admittedly with lot of code borrowed or inspired from - * the full-blown daemon), minimalistic implementation of based daemon, with - * only important aspects being implemented at the moment. - * - * Hopefully easy to adapt for variety of purposes. - * - * NOTE: currently, only cib_rw API end-point is opened, future refinements - * as new modules are added should conditionalize per what the module - * indicates in the context (which is intentionally very loose data glue - * between the skeleton and modules themselves (like CGI variables so - * to say, but more structurally predestined so as to avoid complexities - * of hash table lookups etc.) - */ - -#include -#if 0 -#include "crm/common/ipc_internal.h" /* pcmk__client_t */ -#include "crm/common/xml.h" /* crm_xml_add */ -#endif -#include "crm/msg_xml.h" /* F_SUBTYPE */ -#include "daemons/based/pacemaker-based.h" /* cib_notify_diff */ - -#include /* qb_ipcs_connection_t */ - -#include "based.h" - - -/* direct global access violated in one case only - - mock_based_ipc_accept adds a reference to it to crm_cient_t->userdata */ -mock_based_context_t mock_based_context; - - -/* see based/based_callbacks.c:cib_ipc_accept */ -static int32_t -mock_based_ipc_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) -{ - int32_t ret = 0; - pcmk__client_t *cib_client; - - crm_trace("Connection %p", c); - cib_client = pcmk__new_client(c, uid, gid); - if (cib_client == NULL) { - ret = -EIO; - } - - cib_client->userdata = &mock_based_context; - - return ret; -} - -/* see based/based_callbacks.c:cib_ipc_closed */ -static int32_t -mock_based_ipc_closed(qb_ipcs_connection_t *c) -{ - pcmk__client_t *client = pcmk__find_client(c); - - if (client != NULL) { - crm_trace("Connection %p", c); - pcmk__free_client(client); - } - - return 0; -} - -/* see based/based_callbacks.c:cib_ipc_destroy */ -static void -mock_based_ipc_destroy(qb_ipcs_connection_t *c) -{ - crm_trace("Connection %p", c); - mock_based_ipc_closed(c); -} - -/* see based/based_callbacks.c:cib_process_command (and more) */ -static void -mock_based_handle_query(pcmk__client_t *cib_client, uint32_t flags, - const xmlNode *op_request) -{ - xmlNode *reply, *cib; - const char cib_str[] = -#if 0 -""; -#else -""\ -" "\ -" "\ -" "\ -" "\ -" "\ -" "\ -" "\ -""; -#endif - cib = xmlReadMemory(cib_str, sizeof(cib_str), "file:///tmp/foo", NULL, 0)->children; - - reply = create_xml_node(NULL, "cib-reply"); - crm_xml_add(reply, F_TYPE, T_CIB); - crm_xml_add(reply, F_CIB_OPERATION, - crm_element_value(op_request, F_CIB_OPERATION)); - crm_xml_add(reply, F_CIB_CALLID, - crm_element_value(op_request, F_CIB_CALLID)); - crm_xml_add(reply, F_CIB_CLIENTID, - crm_element_value(op_request, F_CIB_CLIENTID)); - crm_xml_add_int(reply, F_CIB_CALLOPTS, flags); - crm_xml_add_int(reply, F_CIB_RC, pcmk_ok); - - if (cib != NULL) { - crm_trace("Attaching reply output"); - add_message_xml(reply, F_CIB_CALLDATA, cib); - } - - pcmk__ipc_send_xml(cib_client, cib_client->request_id, reply, - ((flags & cib_sync_call)? crm_ipc_flags_none - : crm_ipc_server_event)); - - free_xml(reply); - free_xml(cib); -} - -/* see based/based_callbacks.c:cib_common_callback_worker */ -static void -mock_based_common_callback_worker(uint32_t id, uint32_t flags, - xmlNode *op_request, - pcmk__client_t *cib_client) -{ - const char *op = crm_element_value(op_request, F_CIB_OPERATION); - mock_based_context_t *ctxt; - - if (!strcmp(op, CRM_OP_REGISTER)) { - if (flags & crm_ipc_client_response) { - xmlNode *ack = create_xml_node(NULL, __func__); - crm_xml_add(ack, F_CIB_OPERATION, CRM_OP_REGISTER); - crm_xml_add(ack, F_CIB_CLIENTID, cib_client->id); - pcmk__ipc_send_xml(cib_client, id, ack, flags); - cib_client->request_id = 0; - free_xml(ack); - } - - } else if (!strcmp(op, T_CIB_NOTIFY)) { - int on_off = 0; - const char *type = crm_element_value(op_request, F_CIB_NOTIFY_TYPE); - crm_element_value_int(op_request, F_CIB_NOTIFY_ACTIVATE, &on_off); - - crm_debug("Setting %s callbacks for %s (%s): %s", - type, cib_client->name, cib_client->id, on_off ? "on" : "off"); - - if (!strcmp(type, T_CIB_DIFF_NOTIFY) && on_off) { - pcmk__set_client_flags(cib_client, cib_notify_diff); - } - - ctxt = (mock_based_context_t *) cib_client->userdata; - for (size_t c = ctxt->modules_cnt; c > 0; c--) { - if (ctxt->modules[c - 1]->hooks.cib_notify != NULL) { - ctxt->modules[c - 1]->hooks.cib_notify(cib_client); - } - } - - if (flags & crm_ipc_client_response) { - pcmk__ipc_send_ack(cib_client, id, flags, "ack", CRM_EX_OK); - } - - } else if (!strcmp(op, CIB_OP_QUERY)) { - mock_based_handle_query(cib_client, flags, op_request); - - } else { - crm_notice("Discarded request %s", op); - } -} - -/* see based/based_callbacks.c:cib_ipc_dispatch_rw */ -static int32_t -mock_based_dispatch_command(qb_ipcs_connection_t *c, void *data, size_t size) -{ - uint32_t id = 0, flags = 0; - int call_options = 0; - pcmk__client_t *cib_client = pcmk__find_client(c); - xmlNode *op_request = pcmk__client_data2xml(cib_client, data, &id, &flags); - - crm_notice("Got connection %p", c); - assert(op_request != NULL); - - if (cib_client == NULL || op_request == NULL) { - if (op_request == NULL) { - crm_trace("Invalid message from %p", c); - pcmk__ipc_send_ack(cib_client, id, flags, "nack", CRM_EX_PROTOCOL); - } - return 0; - } - - crm_element_value_int(op_request, F_CIB_CALLOPTS, &call_options); - if (call_options & cib_sync_call) { - assert(flags & crm_ipc_client_response); - cib_client->request_id = id; /* reply only to last in-flight request */ - } - - assert(cib_client->name == NULL); - crm_element_value_int(op_request, F_CIB_CALLOPTS, &call_options); - crm_xml_add(op_request, F_CIB_CLIENTID, cib_client->id); - crm_xml_add(op_request, F_CIB_CLIENTNAME, cib_client->name); - - mock_based_common_callback_worker(id, flags, op_request, cib_client); - free_xml(op_request); - - return 0; -} - -/* * */ - -size_t mock_based_register_module(module_t mod) { - module_t *module; - size_t ret = mock_based_context.modules_cnt++; - - mock_based_context.modules = realloc(mock_based_context.modules, - sizeof(*mock_based_context.modules) - * mock_based_context.modules_cnt); - if (mock_based_context.modules == NULL - || (module = malloc(sizeof(module_t))) == NULL) { - abort(); - } - - memcpy(module, &mod, sizeof(mod)); - mock_based_context.modules[mock_based_context.modules_cnt - 1] = module; - - return ret; -} - -static int -mock_based_options(mock_based_context_t *ctxt, - bool usage, int argc, const char *argv[]) -{ - const char **args2argv; - char *s; - int ret = 0; - - if (argc <= 1) { - const char *help_argv[] = {argv[0], "-h"}; - return mock_based_options(ctxt, false, 2, (const char **) &help_argv); - } - - for (size_t i = 1; i < argc; i++) { - if (argv[i][0] == '-' && argv[i][1] != '-' && argv[i][1] != '\0') { - if (usage) { - printf("\t-%c\t", argv[i][1]); - } - switch(argv[i][1]) { - case 'h': - if (usage) { - printf("show this help message\n"); - ret = 1; - - } else { - if ((args2argv - = malloc((ctxt->modules_cnt + 2) * sizeof(*args2argv))) == NULL - || (s - = malloc((ctxt->modules_cnt * 2 + 2) * sizeof(*s))) == NULL) { - return -1; - } - s[0] = 'h'; - args2argv[ctxt->modules_cnt + 1] = (char[]){'-', 'h', '\0'}; - for (size_t c = ctxt->modules_cnt; c > 0; c--) { - args2argv[c] = (char[]){'-', ctxt->modules[c - 1]->shortopt, '\0'}; - s[(ctxt->modules_cnt - i) + 1] = '|'; - s[(ctxt->modules_cnt - i) + 2] = ctxt->modules[c - 1]->shortopt; - } - s[ctxt->modules_cnt * 2 + 1] = '\0'; - printf("Usage: %s [-{%s}]\n", argv[0], s); - (void) mock_based_options(ctxt, true, 2 + ctxt->modules_cnt, args2argv); - free(args2argv); - free(s); - } - return ret; - default: - for (size_t c = ctxt->modules_cnt; c > 0; c--) { - if (ctxt->modules[c - 1]->shortopt == argv[i][1]) { - ret = ctxt->modules[c - 1]->hooks.argparse(ctxt, usage, argc - i, &argv[i]); - if (ret < 0) { - break; - } else if (ret > 1) { - i += (ret - 1); - } - } - } - if (ret == 0) { - printf("uknown option \"%s\"\n", argv[i]); - } - break; - } - } - } - return ret; -} - -int main(int argc, char *argv[]) -{ - mock_based_context_t *ctxt = &mock_based_context; - - if (mock_based_options(ctxt, false, argc, (const char **) argv) > 0) { - struct qb_ipcs_service_handlers cib_ipc_callbacks = { - .connection_accept = mock_based_ipc_accept, - .connection_created = NULL, - .msg_process = mock_based_dispatch_command, - .connection_closed = mock_based_ipc_closed, - .connection_destroyed = mock_based_ipc_destroy, - }; - crm_log_preinit(NULL, argc, argv); - crm_log_init(NULL, LOG_DEBUG, false, true, argc, argv, false); - qb_ipcs_service_t *ipcs_command = - mainloop_add_ipc_server(PCMK__SERVER_BASED_RW, QB_IPC_NATIVE, - &cib_ipc_callbacks); - g_main_loop_run(g_main_loop_new(NULL, false)); - qb_ipcs_destroy(ipcs_command); - } - - for (size_t c = ctxt->modules_cnt; c > 0; c--) { - if (ctxt->modules[c - 1]->hooks.destroy != NULL) { - ctxt->modules[c - 1]->hooks.destroy(ctxt->modules[c - 1]); - } - free(mock_based_context.modules[c - 1]); - } - - free(mock_based_context.modules); -} diff --git a/maint/mocked/based.h b/maint/mocked/based.h deleted file mode 100644 index c214c0858b..0000000000 --- a/maint/mocked/based.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2019-2020 the Pacemaker project contributors - * - * The version control history for this file may have further details. - * - * Licensed under the GNU General Public License version 2 or later (GPLv2+). - */ - -#pragma once - -#include /* size_t */ -#include /* bool */ - -#include /* pcmk__client_t */ - - -struct module_s; - -typedef struct mock_based_context_s { - size_t modules_cnt; - struct module_s** modules; -} mock_based_context_t; - - -typedef int (*mock_based_argparse_hook)(mock_based_context_t *, - bool, int, - const char *[]); - -typedef void (*mock_based_destroy_hook)(struct module_s *); - -/* specialized callbacks... */ -typedef void (*mock_based_cib_notify_hook)(pcmk__client_t *); - -typedef struct mock_based_hooks_s { - /* generic ones */ - mock_based_argparse_hook argparse; - mock_based_destroy_hook destroy; - - /* specialized callbacks... */ - mock_based_cib_notify_hook cib_notify; -} mock_based_hooks_t; - -typedef struct module_s { - char shortopt; - mock_based_hooks_t hooks; - void *priv; -} module_t; - -size_t mock_based_register_module(module_t mod);