Commit 01fb9fd3 authored by Michal Nowikowski's avatar Michal Nowikowski
Browse files

rearranges docs and build script

Two reasons:
- sphinx requires to have all sources in its source directory
  so man pages have to be moved
- layout of subfolders in doc folder was not consistent
parent 556016fc
......@@ -1566,9 +1566,8 @@ fi
SUBDIRS = guide docgen
EXTRA_DIST = Doxyfile Doxyfile-xml
EXTRA_DIST += devel/bison.dox
EXTRA_DIST += devel/config-backend.dox
EXTRA_DIST += devel/contribute.dox
EXTRA_DIST += devel/mainpage.dox
EXTRA_DIST += devel/terminology.dox
EXTRA_DIST += devel/unit-tests.dox
EXTRA_DIST += devel/doc.dox
EXTRA_DIST += devel/congestion-handling.dox
SUBDIRS = devel sphinx
nobase_dist_doc_DATA = examples/agent/comments.json
nobase_dist_doc_DATA += examples/agent/simple.json
......@@ -77,153 +67,3 @@ nobase_dist_doc_DATA += examples/netconf/kea-dhcp6-operations/twosubnets.xml
nobase_dist_doc_DATA += examples/netconf/comments.json
nobase_dist_doc_DATA += examples/netconf/simple-dhcp4.json
nobase_dist_doc_DATA += examples/netconf/simple-dhcp6.json
# These are files that document our APIs. They're not really needed as the
# content is included in the api.xml, but may be useful for people who
# want to document the API.
EXTRA_DIST += api/build-report.json
EXTRA_DIST += api/cache-clear.json api/cache-get.json
EXTRA_DIST += api/cache-get-by-id.json api/cache-insert.json
EXTRA_DIST += api/cache-load.json api/cache-remove.json
EXTRA_DIST += api/cache-size.json api/cache-write.json
EXTRA_DIST += api/class-add.json api/class-del.json
EXTRA_DIST += api/class-get.json api/class-list.json
EXTRA_DIST += api/class-update.json
EXTRA_DIST += api/config-get.json api/config-reload.json
EXTRA_DIST += api/config-set.json api/config-test.json
EXTRA_DIST += api/config-write.json api/dhcp-disable.json
EXTRA_DIST += api/dhcp-enable.json api/ha-continue.json
EXTRA_DIST += api/ha-heartbeat.json api/ha-scopes.json
EXTRA_DIST += api/ha-sync.json api/lease4-add.json
EXTRA_DIST += api/lease4-del.json api/lease4-get-all.json
EXTRA_DIST += api/lease4-get.json api/lease4-update.json
EXTRA_DIST += api/lease4-wipe.json api/lease6-add.json
EXTRA_DIST += api/lease6-bulk-apply.json
EXTRA_DIST += api/lease6-del.json api/lease6-get-all.json
EXTRA_DIST += api/lease6-get.json api/lease6-update.json
EXTRA_DIST += api/lease6-wipe.json api/leases-reclaim.json
EXTRA_DIST += api/libreload.json api/list-commands.json
EXTRA_DIST += api/network4-add.json api/network4-del.json
EXTRA_DIST += api/network4-get.json api/network4-list.json
EXTRA_DIST += api/network4-subnet-add.json api/network4-subnet-del.json
EXTRA_DIST += api/network6-add.json api/network6-del.json
EXTRA_DIST += api/network6-get.json api/network6-list.json
EXTRA_DIST += api/network6-subnet-add.json api/network6-subnet-del.json
EXTRA_DIST += api/remote-global-parameter4-del.json
EXTRA_DIST += api/remote-global-parameter4-get-all.json
EXTRA_DIST += api/remote-global-parameter4-get.json
EXTRA_DIST += api/remote-global-parameter4-set.json
EXTRA_DIST += api/remote-global-parameter6-del.json
EXTRA_DIST += api/remote-global-parameter6-get-all.json
EXTRA_DIST += api/remote-global-parameter6-get.json
EXTRA_DIST += api/remote-global-parameter6-set.json
EXTRA_DIST += api/remote-network4-del.json
EXTRA_DIST += api/remote-network4-get.json
EXTRA_DIST += api/remote-network4-list.json
EXTRA_DIST += api/remote-network4-set.json
EXTRA_DIST += api/remote-network6-del.json
EXTRA_DIST += api/remote-network6-get.json
EXTRA_DIST += api/remote-network6-list.json
EXTRA_DIST += api/remote-network6-set.json
EXTRA_DIST += api/remote-option-def4-del.json
EXTRA_DIST += api/remote-option-def4-get-all.json
EXTRA_DIST += api/remote-option-def4-get.json
EXTRA_DIST += api/remote-option-def4-set.json
EXTRA_DIST += api/remote-option-def6-del.json
EXTRA_DIST += api/remote-option-def6-get-all.json
EXTRA_DIST += api/remote-option-def6-get.json
EXTRA_DIST += api/remote-option-def6-set.json
EXTRA_DIST += api/remote-option4-global-del.json
EXTRA_DIST += api/remote-option4-global-get-all.json
EXTRA_DIST += api/remote-option4-global-get.json
EXTRA_DIST += api/remote-option4-global-set.json
EXTRA_DIST += api/remote-option6-global-del.json
EXTRA_DIST += api/remote-option6-global-get-all.json
EXTRA_DIST += api/remote-option6-global-get.json
EXTRA_DIST += api/remote-option6-global-set.json
EXTRA_DIST += api/remote-subnet4-del-by-id.json
EXTRA_DIST += api/remote-subnet4-del-by-prefix.json
EXTRA_DIST += api/remote-subnet4-get-by-id.json
EXTRA_DIST += api/remote-subnet4-get-by-prefix.json
EXTRA_DIST += api/remote-subnet4-list.json
EXTRA_DIST += api/remote-subnet4-set.json
EXTRA_DIST += api/remote-subnet6-del-by-id.json
EXTRA_DIST += api/remote-subnet6-del-by-prefix.json
EXTRA_DIST += api/remote-subnet6-get-by-id.json
EXTRA_DIST += api/remote-subnet6-get-by-prefix.json
EXTRA_DIST += api/remote-subnet6-list.json
EXTRA_DIST += api/remote-subnet6-set.json
EXTRA_DIST += api/reservation-add.json api/reservation-del.json
EXTRA_DIST += api/reservation-get.json api/reservation-get-all.json
EXTRA_DIST += api/reservation-get-page.json
EXTRA_DIST += api/server-tag-get.json api/shutdown.json
EXTRA_DIST += api/remote-server4-del.json
EXTRA_DIST += api/remote-server4-get.json
EXTRA_DIST += api/remote-server4-get-all.json
EXTRA_DIST += api/remote-server4-set.json
EXTRA_DIST += api/remote-server6-del.json
EXTRA_DIST += api/remote-server6-get.json
EXTRA_DIST += api/remote-server6-get-all.json
EXTRA_DIST += api/remote-server6-set.json
EXTRA_DIST += api/statistic-get-all.json api/statistic-get.json
EXTRA_DIST += api/statistic-remove-all.json api/statistic-remove.json
EXTRA_DIST += api/statistic-reset-all.json api/statistic-reset.json
EXTRA_DIST += api/stat-lease4-get.json api/stat-lease6-get.json
EXTRA_DIST += api/subnet4-add.json api/subnet4-del.json
EXTRA_DIST += api/subnet4-get.json api/subnet4-list.json
EXTRA_DIST += api/subnet4-update.json
EXTRA_DIST += api/subnet6-add.json api/subnet6-del.json
EXTRA_DIST += api/subnet6-get.json api/subnet6-list.json
EXTRA_DIST += api/subnet6-update.json
EXTRA_DIST += api/_template.json api/version-get.json
mkdir -p html
(cat Doxyfile; echo PROJECT_NUMBER=$(PACKAGE_VERSION)) | doxygen - > html/doxygen.log 2> html/doxygen-error.log
echo `grep -i ": warning:" html/doxygen-error.log | wc -l` warnings/errors detected.
rm -rf html
# There are several steps needed to document new API command:
# 1. edit docgen/cmds-list and add the new command
# 2. ./configure --enable-generate-docs
# 3. make - you need to build the sources first, am afraid. The reason why you
# need to do this is that the tool kea-docgen depends on libkea-cc as it
# loads JSON files. This means that the libs need to be built first.
# 4. (optional) run: make templates
# This will go through the list of commands listed in cmds-list
# and will check if there are corresponding JSON files in api/name.json
# If the file is missing, a new JSON will be created using template.
# If you dislike this generator, you can always use api/_template.json
# and copy it over under the name of a new command.
# 5. Edit api/command-name.json. If the command is provided by the daemon
# out of its own (and not via hook), simply delete the hook entry.
# If you don't want to provide command syntax (cmd-syntax key),
# any comments about the syntax (cmd-comment key) or response syntax
# (resp-syntax) or any comment about response (resp-comment), simply
# remove those unused keys. The generator will attempt to generate
# boilerplates for it.
# 6. Generate api.xml: make api
# 7. Rebuild User's Guide as usual: make guide
# This target will generate templates. There's no need to run it, unless
# new commands have been added or there are existing commands that are
# still not documented.
templates: docgen
docgen/generate-templates docgen/cmds-list
# This will generate the api.xml file using docgen generator. It will
# read the JSON files from api/ directory. Make sure they're up to date.
api: docgen
docgen/kea-docgen api/*.json
# This convenience target makes sure the docgen tool is built properly
$(MAKE) -C docgen
# That's a bit of a hack, but we are making sure that devel target
# is always valid. The alternative is to make devel depend on all
# *.cc *.h files in the whole tree.
.PHONY: devel guide docgen
Data Source Library Classes
About this document
This memo describes major classes used in the data source library,
mainly focusing on handling in-memory cache with consideration of the
shared memory support. It will give an overview of the entire design
architecture and some specific details of how these classes are expected
to be used.
Before reading, the higher level inter-module protocol should be understood:
Overall relationships between classes
The following diagram shows major classes in the data source library
related to in-memory caches and their relationship.
image::overview.png[Class diagram showing overview of relationships]
Major design decisions of this architecture are:
* Keep each class as concise as possible, each focusing on one or
small set of responsibilities. Smaller classes are generally easier
to understand (at the cost of understanding how they work in the
"big picture" of course) and easier to test.
* On a related point, minimize dependency to any single class. A
monolithic class on which many others are dependent is generally
difficult to maintain because you'll need to ensure a change to the
monolithic class doesn't break anything on any other classes.
* Use polymorphism for any "fluid" behavior, and hide specific details
under abstract interfaces so implementation details won't be
directly referenced from any other part of the library.
Specifically, the underlying memory segment type (local, mapped, and
possibly others) and the source of in-memory data (master file or
other data source) are hidden via a kind of polymorphism.
* Separate classes directly used by applications from classes that
implement details. Make the former classes as generic as possible,
agnostic about implementation specific details such as the memory
segment type (or, ideally and where possible, whether it's for
in-memory cache or the underlying data source).
The following give a summarized description of these classes.
* `ConfigurableClientList`: The front end to application classes. An
application that uses the data source library generally maintains
one or more `ConfigurableClientList` object (usually one per RR
class, or when we support views, probably one per view). This class
is a container of sets of data source related classes, providing
accessor to these classes and also acting as a factory of other
related class objects. Note: Due to internal implementation
reasons, there is a base class for `ConfigurableClientList` named
`ClientList` in the C++ version, and applications are expected to
use the latter. But conceptually `ConfigurableClientList` is an
independent value class; the inheritance is not for polymorphism.
Note also that the Python version doesn't have the base class.
* `DataSourceInfo`: this is a straightforward tuple of set of class
objects corresponding to a single data source, including
`DataSourceClient`, `CacheConfig`, and `ZoneTableSegment`.
`ConfigurableClientList` maintains a list of `DataSourceInfo`, one
for each data source specified in its configuration.
* `DataSourceClient`: The front end class to applications for a single
data source. Applications will get a specific `DataSourceClient`
object by `ConfigurableClientList::find()`.
`DataSourceClient` itself is a set of factories for various
operations on the data source such as lookup or update.
* `CacheConfig`: library internal representation of in-memory cache
configuration for a data source. It knows which zones are to be
cached and where the zone data (RRs) should come from, either from a
master file or other data source. With this knowledge it will
create an appropriate `LoadAction` object. Note that `CacheConfig`
isn't aware of the underlying memory segment type for the in-memory
data. It's intentionally separated from this class (see the
conciseness and minimal-dependency design decisions above).
* `ZoneTableSegment`: when in-memory cache is enabled, it provides
memory-segment-type independent interface to the in-memory data.
This is an abstract base class (see polymorphism in the design
decisions) and inherited by segment-type specific subclasses:
`ZoneTableSegmentLocal` and `ZoneTableSegmentMapped` (and possibly
others). Any subclass of `ZoneTableSegment` is expected to maintain
the specific type of `MemorySegment` object.
* `ZoneWriter`: a frontend utility class for applications to update
in-memory zone data (currently it can only load a whole zone and
replace any existing zone content with a new one, but this should be
extended so it can handle partial updates).
Applications will get a specific `ZoneWriter`
object by `ConfigurableClientList::getCachedZoneWriter()`.
`ZoneWriter` is constructed with `ZoneableSegment` and `LoadAction`.
Since these are abstract classes, `ZoneWriter` doesn't have to be
aware of "fluid" details. It's only responsible for "somehow" preparing
`ZoneData` for a new version of a specified zone using `LoadAction`,
and installing it in the `ZoneTable` (which can be accessed via
* `DataSourceStatus`: created by `ConfigurableClientList::getStatus()`,
a straightforward tuple that represents some status information of a
specific data source managed in the `ConfigurableClientList`.
`getStatus()` generates `DataSourceStatus` for all data sources
managed in it, and returns them as a vector.
* `ZoneTableAccessor`, `ZoneTableIterator`: frontend classes to get
access to the conceptual "zone table" (a set of zones) stored in a
specific data source. In particular, `ZoneTableIterator` allows
applications to iterate over all zones (by name) stored in the
specific data source.
Applications will get a specific `ZoneTableAccessor`
object by `ConfigurableClientList::getZoneTableAccessor()`,
and get an iterator object by calling `getIterator` on the accessor.
These are abstract classes and provide unified interfaces
independent from whether it's for in-memory cached zones or "real"
underlying data source. But the initial implementation only
provides the in-memory cache version of subclass (see the next
* `ZoneTableAccessorCache`, `ZoneTableIteratorCache`: implementation
classes of `ZoneTableAccessor` and `ZoneTableIterator` for in-memory
cache. They refer to `CacheConfig` to get a list of zones to be
* `ZoneTableHeader`, `ZoneTable`: top-level interface to actual
in-memory data. These were separated based on a prior version of
the design ( where
`ZoneTableHeader` may contain multiple `ZoneTable`s. It's
one-to-one relationship in the latest version (of implementation),
so we could probably unify them as a cleanup.
* `ZoneData`: representing the in-memory content of a single zone.
`ZoneTable` contains (zero, one or) multiple `ZoneData` objects.
* `RdataSet`: representing the in-memory content of (data of) a single
`ZoneData` contains `RdataSet`s corresponding to the RRsets stored
in the zone.
* `LoadAction`: a "polymorphic" functor that implements loading zone
data into memory. It hides from its user (i.e., `ZoneWriter`)
details about the source of the data: master file or other data
source (and perhaps some others). The "polymorphism" is actually
realized as different implementations of the functor interface, not
class inheritance (but conceptually the effect and goal is the
same). Note: there's a proposal to replace `LoadAction` with
a revised `ZoneDataLoader`, although the overall concept doesn't
change. See Trac ticket #2912.
* `ZoneDataLoader` and `ZoneDataUpdater`: helper classes for the
`LoadAction` functor(s). These work independently from the source
of data, taking a sequence of RRsets objects, converting them
into the in-memory data structures (`RdataSet`), and installing them
into a newly created `ZoneData` object.
Sequence for auth module using local memory segment
In the remaining sections, we explain how the classes shown in the
previous section work together through their methods for commonly
intended operations.
The following sequence diagram shows the case for the authoritative
DNS server module to maintain "local" in-memory data. Note that
"auth" is a conceptual "class" (not actually implemented as a C++
class) to represent the server application behavior. For the purpose
of this document that should be sufficient. The same note applies to
all examples below.
image::auth-local.png[Sequence diagram for auth server using local memory segment]
1. On startup, the auth module creates a `ConfigurableClientList`
for each RR class specified in the configuration for "data_sources"
module. It then calls `ConfigurableClientList::configure()`
for the given configuration of that RR class.
2. For each data source, `ConfigurableClientList` creates a
`CacheConfig` object with the corresponding cache related
3. If in-memory cache is enabled for the data source,
`ZoneTableSegment` is also created. In this scenario the cache
type is specified as "local" in the configuration, so a functor
creates `ZoneTableSegmentLocal` as the actual instance.
In this case its `ZoneTable` is immediately created, too.
4. `ConfigurableClientList` checks if the created `ZoneTableSegment` is
writable. It is always so for "local" type of segments. So
`ConfigurableClientList` immediately loads zones to be cached into
memory. For each such zone, it first gets the appropriate
`LoadAction` through `CacheConfig`, then creates `ZoneWriter` with
the `LoadAction`, and loads the data using the writer.
5. If the auth module receives a "reload" command for a cached zone
from other module (xfrin, an end user, etc), it calls
`ConfigurableClientList::getCachedZoneWriter` to load and install
the new version of the zone. The same loading sequence takes place
except that the user of the writer is the auth module.
Also, the old version of the zone data is destroyed at the end of
the process.
Sequence for auth module using mapped memory segment
This is an example for the authoritative server module that uses
mapped type memory segment for in-memory data.
image::auth-mapped.png[Sequence diagram for auth server using mapped memory segment]
1. The sequence is the same to the point of creating `CacheConfig`.
2. But in this case a `ZoneTableSegmentMapped` object is created based
on the configuration of the cache type. This type of
`ZoneTableSegment` is initially empty and isn't even associated
with a `MemorySegment` (and therefore considered non-writable).
3. `ConfigurableClientList` checks if the zone table segment is
writable to know whether to load zones into memory by itself,
but as `ZoneTableSegment::isWritable()` returns false, it skips
the loading.
4. The auth module gets the status of each data source, and notices
there's a `WAITING` state of segment. So it subscribes to the
"Memmgr" group on a command session and waits for an update
from the memory manager (memmgr) module. (See also the note at the
end of the section)
5. When the auth module receives an update command from memmgr, it
calls `ConfigurableClientList::resetMemorySegment()` with the command
argument and the segment mode of `READ_ONLY`.
Note that the auth module handles the command argument as mostly
opaque data; it's not expected to deal with details of segment
type-specific behavior. If the reset fails, auth aborts (as there's
no clear way to handle the failure).
6. `ConfigurableClientList::resetMemorySegment()` subsequently calls
`reset()` method on the corresponding `ZoneTableSegment` with the
given parameters.
In the case of `ZoneTableSegmentMapped`, it creates a new
`MemorySegment` object for the mapped type, which internally maps
the specific file into memory.
memmgr is expected to have prepared all necessary data in the file,
so all the data are immediately ready for use (i.e., there
shouldn't be any explicit load operation).
7. When a change is made in the mapped data, memmgr will send another
update command with parameters for new mapping. The auth module
calls `ConfigurableClientList::resetMemorySegment()`, and the
underlying memory segment is swapped with a new one. The old
memory segment object is destroyed. Note that
this "destroy" just means unmapping the memory region; the data
stored in the file are intact. Again, if mapping fails, auth
8. If the auth module happens to receive a reload command from other
module, it could call
to reload the data by itself, just like in the previous section.
In this case, however, the writability check of
`getCachedZoneWriter()` fails (the segment was created as
`READ_ONLY` and is non-writable), so loading won't happen.
NOTE: While less likely in practice, it's possible that the same auth
module uses both "local" and "mapped" (and even others) type of
segments for different data sources. In such cases the sequence is
either the one in this or previous section depending on the specified
segment type in the configuration. The auth module itself isn't aware
of per segment-type details, but changes the behavior depending on the
segment state of each data source at step 4 above: if it's `WAITING`,
it means the auth module needs help from memmgr (that's all the auth
module should know; it shouldn't be bothered with further details such
as mapped file names); if it's something else, the auth module doesn't
have to do anything further.
Sequence for memmgr module initialization using mapped memory segment
This sequence shows the common initialization sequence for the
memory manager (memmgr) module using a mapped type memory segment.
This is a mixture of the sequences shown in Sections 2 and 3.
1. Initial sequence is the same until the application module (memmgr)
calls `ConfigurableClientList::getStatus()` as that for the
previous section.
2. The memmgr module identifies the data sources whose in-memory cache
type is "mapped". (Unlike other application modules, the memmgr
should know what such types means due to its exact responsibility).
For each such data source, it calls
`ConfigurableClientList::resetMemorySegment` with the READ_WRITE
mode and other mapped-type specific parameters. memmgr should be
able to generate the parameters from its own configuration and
other data source specific information (such as the RR class and
data source name).
3. The `ConfigurableClientList` class calls
`ZoneTableSegment::reset()` on the corresponding zone table
segment with the given parameters. In this case, since the mode is
READ_WRITE, a new `ZoneTable` will be created (assuming this is a
very first time initialization; if there's already a zone table
in the segment, it will be used).
4. The memmgr module then calls
`ConfigurableClientList::getZoneTableAccessor()`, and calls the
`getIterator()` method on it to get a list of zones for which
zone data are to be loaded into the memory segment.
5. The memmgr module loads the zone data for each such zone. This
sequence is the same as shown in Section 2.
6. On loading all zone data, the memmgr module sends an update command
to all interested modules (such as auth) in the segment, and waits
for acknowledgment from all of them.
7. Then it calls `ConfigurableClientList::resetMemorySegment()` for
this data source with almost the same parameter as step 2 above,
but with a different mapped file name. This will make a swap of
the underlying memory segment with a new mapping. The old
`MemorySegment` object will be destroyed, but as explained in the
previous section, it simply means unmapping the file.
8. The memmgr loads the zone data into the newly mapped memory region
by repeating the sequence shown in step 5.
9. The memmgr repeats all this sequence for data sources that use
"mapped" segment for in-memory cache. Note: it could handle
multiple data sources in parallel, e.g., while waiting for
acknowledgment from other modules.
Sequence for memmgr module to reload a zone using mapped memory segment
This example is a continuation of the previous section, describing how
the memory manager reloads a zone in mapped memory segment.
1. When the memmgr module receives a reload command from other module,
it calls `ConfigurableClientList::getCachedZoneWriter()` for the
specified zone name. This method checks the writability of
the segment, and since it's writable (as memmgr created it in the
READ_WRITE mode), `getCachedZoneWriter()` succeeds and returns
a `ZoneWriter`.
2. The memmgr module uses the writer to load the new version of zone
data. There is nothing specific to mapped-type segment here.
3. The memmgr module then sends an update command to other modules
that would share this version, and waits for acknowledgment from
all of them.
4. On getting acknowledgments, the memmgr module calls
`ConfigurableClientList::resetMemorySegment()` with the parameter
specifying the other mapped file. This will swap the underlying
`MemorySegment` with a newly created one, mapping the other file.
5. The memmgr updates this segment, too, so the two files will contain
the same version of data.
participant memmgr as ":b10-memmgr"
[-> memmgr: new/initial config\n(datasrc cfg)
activate memmgr
participant list as ":Configurable\nClientList"
create list
memmgr -> list: <<construct>>
memmgr -> list: configure(cfg)
activate list
participant CacheConfig as ":CacheConfig"
create CacheConfig
list -> CacheConfig: <<construct>> (cfg)
participant zt_segment as ":ZoneTable\nSegment\n(Mapped)"
create zt_segment
list -> zt_segment: <<construct>>
list -> zt_segment: isWritable()
activate zt_segment
note over zt_segment: Segment not writable\nwhen not reset
zt_segment --> list: false
deactivate zt_segment
deactivate list
memmgr -> list: getStatus()
activate list
list --> memmgr: DataSourceStatus[]
deactivate list
loop for each datasrc with mapped segment
memmgr -> list: resetMemorySegment\n(datasrc_name,\nREAD_WRITE,\nsegmentparam)
activate list
list -> zt_segment: reset\n(READ_WRITE,\nsegmentparam)
activate zt_segment
participant segment as "seg1:Memory\nSegment\n(Mapped)"
create segment
zt_segment -> segment: <<construct>>
participant segment.2 as "seg2:Memory\nSegment\n(Mapped)"
participant ZoneTable as ":ZoneTable"
create ZoneTable
zt_segment -> ZoneTable: <<construct>>
deactivate zt_segment
deactivate list
memmgr -> list: getZoneTableAccessor\n(datasrc_name,\ncache=true)
activate list
list -> memmgr: ZoneTableAccessor
deactivate list
loop for each zone given by ZoneTableIterator
memmgr -> list: getCachedZoneWriter\n(zone_name)
activate list
list -> CacheConfig: getLoadAction()
activate CacheConfig
participant LoadAction as "la:LoadAction"
create LoadAction
CacheConfig -> LoadAction: <<construct>>
CacheConfig --> list : la
deactivate CacheConfig
participant ZoneWriter as "zw:ZoneWriter"