blob: e480979b87f1fe406549c6f45492d4a233b3abc8 [file] [log] [blame]
%
% Licensed to the Apache Software Foundation (ASF) under one
% or more contributor license agreements. See the NOTICE file
% distributed with this work for additional information
% regarding copyright ownership. The ASF licenses this file
% to you under the Apache License, Version 2.0 (the
% "License"); you may not use this file except in compliance
% with the License. You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing,
% software distributed under the License is distributed on an
% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
% KIND, either express or implied. See the License for the
% specific language governing permissions and limitations
% under the License.
%
\section{DUCC Database Integration}
DUCC is integrated with the Apache Cassandra database (\url{https://cassandra.apache.org/}. As of
DUCC release 2.1.0 the database is used for the following functions:
\begin{itemize}
\item History. Previously a history file for all work in the system was written to the
DUCC {\em history} directory. These files are now written to the database. As of this
writing, we write the serialized DUCC objects as blobs for future reference with
several tables summarizing the contents of the blob for use by command-line utilities
and the webserver.
\item Service registry. Previously, the service registry was maintained as a collection of
Java {\em properties} files in the DUCC {\em state} directory. As of 2.1.0, the registry
is maintained in a set of database tables.
\item Service registry history. Previously, when a service was unregistered, it's registry
files were moved to the DUCC {\em history} directory. As of 2.1.0, a property in
the database tables for the registry is updated to indicate the entry is archived.
\item Orchestrator checkpoint. Previously, the DUCC Orchestrator would write a file
containing the state of all active work in the system, used for restart of the system.
As of 2.1.0, this checkpoint file is written as a BLOB to the database.
\item Resource Manager dynamic state. Previously, this was not persisted. As of 2.1.0,
the current state of all hosts in the system, and all work scheduled on these hosts
is maintained in the database. This state is deleted when the RM starts, and is rebuilt
or updated as nodes check in to the RM and as work enters and leaves.
\end{itemize}
\section{Code Organization}
\paragraph{Dependencies} All code that interfaces with the database resides in a single project,
in a single directory in the DUCC source, {\em uima-ducc-database}. All access to this function
by the DUCC daemons is through interfaces. There are no compile-time dependencies on this
project by other DUCC projects; conversely, this project has compile-time dependencies only on
the low-level common code: {\em uima-ducc-common, uima-ducc-transport, and uima-ducc-user}.
Figure ~\ref{fig:db-structure} provides a visual overview of the Database component structure.
\begin{figure}[H]
\centering
\includegraphics[width=5.5in]{images/ducc-internals/db-structure.png}
\caption{Database Structure}
\label{fig:db-structure}
\end{figure}
Runtime dependencies are resolved with reflection. Entries in ducc.properties
are used to specify the classes which interface with the database. The DUCC scripting
insures that the CLASSPATHs of components using the database contain the
necessary entries.
\paragraph{Factories}
The Cassandra Java client is thread-safe and manages connection pooling. Only a single
Cassandra {\em session} should be acquired for all threads in a process. To enforce this
the {\em Factory} pattern is used to acquire database handles. The factory creates
a single static handle and returns this singleton on every call.
The objects returned by the factories are referenced through their interfaces that describe all
legal actions against the persistent store.
There are three factories:
\begin{description}
\item[HistoryFactory.java] This resides in
\begin{verbatim}
uima-ducc-transport/src/main/java/org/apache/uima/ducc/transport/event/common/history
\end{verbatim}
This is used by the Orchestrator to write history files and its checkpoint, and to restore
the checkpoint on startups. It is used by the Web Server to read work history.
\item[StateServicesFactory.java] This resides in
\begin{verbatim}
uima-ducc-common/src/main/java/org/apache/uima/ducc/common/persistence/services
\end{verbatim}
This is used by the Service Manager to maintaining service registrations, service metadata,
and service registration history. It is used by the Web Server to show the service
registration and meta details.
\item[RmPersistenceFactory.java] This resides in
\begin{verbatim}
uima-ducc-common/src/main/java/org/apache/uima/ducc/common/persistence/rm
\end{verbatim}
This is used by the Resource Manager to maintain its internal scheduling state for the
purpose of inspection by other agents. This is used by the Web Server to show machine
details. This is used by the admin CLI to show the state of all machines and work in the
system.
\end{description}
\paragraph{Interfaces}
All higher-level communication to the database is done through objects returned from the
factories which must conform to specific interfaces. There are three interfaces:
\begin{description}
\item[IHistoryPersistenceManager.java] This resides in
\begin{verbatim}
uima-ducc-transport/src/main/java/org/apache/uima/ducc/transport/event/common/history
\end{verbatim}
See its Javadoc for details of its calling sequence.
\item[IStateServices.java] This resides in
\begin{verbatim}
uima-ducc-common/src/main/java/org/apache/uima/ducc/common/persistence/services
\end{verbatim}
See its Javadoc for details of its calling sequence.
\item[IRmPersistence.java] This resides in
\begin{verbatim}
uima-ducc-common/src/main/java/org/apache/uima/ducc/common/persistence/rm
\end{verbatim}
See its Javadoc for details of its calling sequence.
\end{description}
In addition to the calling sequences, these interfaces contain Java {\em enum} structures that
describe the database schema. See below for how these enums are designed.
\paragraph{Implementations}
Multiple implementations of each interface are provided. In all cases, a ``null''
implementation for which all methods are empty stubs is used as a fallback in the event that a
more functional interface cannot be provided. There are both {\em file-based} and {\em
database-based} implementations for Orchestrator state and for the Service registry. Resource
manager state is provided via the database only. See the DuccBook for details on how to select
a specific implementation at runtime.
In the case of the implementations that interface with the database, an additional method is
required, but is not part of the public interface:
\begin{verbatim}
static RETURN-TYPE mkSchema();
\end{verbatim}
The specific type of object returned by this method varies with database implementations. It
must return a collection of objects that the database creation methods can use to create the
database schema.
\section{Database Schema}
The schema for all tables is controlled by Java {\em enum} objects in the various interfaces. These enums must adhere to a specific
interface, defined in
\begin{verbatim}
uima-ducc-common/src/main/java/org/apache/uima/ducc/common/persistence/IDbProperty.java
\end{verbatim}
There are five methods defined in this interface, used by the database package to automatically generate
the schema. These interfaces may also be used by applications when querying the database to determine
the types and actual database column names for each table.
Most elements in the enum define columns of a table in the database. Methods on the enum
contain meta-data required to correctly create and interpret the data in a column. Some elements in the
enum are meta-data about the column itself.
These methods are:
\begin{description}
\item[String pname()] This the name of a column as known by DUCC and may contain any ASCII
characters. Note this need not be the name of the column in the database.
\item[String columnName()] This is the name of the column as used in the database. It must
conform to the column-naming standards of the database being used.
\item[Type type()] This specifies the type of data in the column. Rather than specifying the
database-specific type names, we supply an abstract name in the Type object which the
database package translates to the correct form for the specific database implementation.
\item[isPrimaryKey()] If true, the data in the column defined by this enum is a primary key.
It is legal to specify multiple columns as primary keys, in which case, the database
component will create a compound primary key. The keys are generated in the order
they occur in the enum.
\item[boolean isPrivate()] This enum element is used by the database package only and
should never be passed back to applications. It allows the
database package to maintain table-specific information that is not accidentally
translated into a return element. For example this is used when a row corresponds to
a collection of Java properties, but the enum does not correspond to one of the
returned properties.
\item[boolean isMeta()] This is the converse of isPrivate(). This allows
an application to pass information to the database component that does not get
placed into the schema or database. For example, the name of the table may be
defined in the enum, but this should not become the name of a column in any table.
\item[isIndex()] If {\em true}, this column is indexed in the schema. Multiple columns
may be specified for indexing.
\end{description}
\paragraph{Types} We maintain a level of indirection between DUCC and specific database types, to enable
disparate database implementation from a common meta-schema. The DUCC-defined database
types are:
\begin{description}
\item[String] The database implementation translates this into the appropriate
type for the database, for example, {\em varchar} for a DB2 database.
\item[Blob] This specifies a binary large object, e.g. a serialized
Java object.
\item[Integer] This specifies a 32-bit integer.
\item[Long] This specifies a 64-bit integer.
\item[Double] This specifies a Java object of type {\em double}
\item[UUID] Some modern databases have native support for Java UUIDs. This specifies
an object conforming to that type. Older databases may translate this to {\em char}
or {\em varchar}.
\end{description}
\section{The uima-ducc-database package}
This package is intended to be isolated as much as possible from the rest of DUCC. The
design-point is that it should be mostly straightforward to change the database implementation,
or to create additional persistence implementations, as long as the functions described
in the previous sections are maintained.
\paragraph{Database Core} Most of the database interface is contained in two classes:
\begin{description}
\item[DbManager] This object is responsible for directly interfacing with the
specific database implementation. It knows how to manage the
database URL, how to contact the database, how to execute commands (e.g. SQL)
against the database, how to create users and manage security, and the
general structure of the DB API.
This object is to be used only to initiate database communication. It generally does not
know much about the specific query language used (e.g. CQL vs SQL), which is left to the
DbHandle.
\item[DbHandle] This provides a level of indirection between {\em clients} of the
database, and the {\em implementation} of the database. A {\em client}
instantiates a DbManager and then requests a {\em DbHandle} whenever it actually
needs to communicate to the DB. If session pooling is supported, the DbHandle should
transparently enable this so higher-level layers need not be concerned with it.
The handle rarely communicates directly with the database itself. Instead, it
requests the DbManager that created it to do actual communication.
\end{description}
\paragraph{Bootstrap modules} Some specialized functions are separated into discrete classes:
\begin{description}
\item[DbAlive] This module communicates directly with the database, bypassing both
the DbManager and DbHandle. It is considered a {\em bootstrap} object. It assumes
the database has been started, and attempts to contact it, determine if the
{\em ducc} and {\em guest} userids are defined, and queries the schema. This
implements retry logic as the database can take time to start up. It bypasses
the DbManager because if the database is in some way compromised, it may not be
possible to successfully instantiate a DbManager or DbHandle.
\item[DbCreate] This module also bypasses the DbManager and communicates directly
with the database. It creates the {\em ducc} superuser id, disables the
default superuser, and creates a restricted {\em guest} userid.
This is also considered a bootstrap object.
\item[DbLoader] This module is used to load an existing DUCC file-based {\em history,
checkpoint,} and {\em service registry} into the database. It is considered a bootstrap
module and communicates directly to the database when it can for best performance, and and
indirectly through the implementations of the DUCC persistence objects to create summary
tables of the various objects.
\end{description}
\paragraph{Schema Creation}
Each of the DUCC-component-specific database implementations must implement a method
\begin{verbatim}
static RETURN-TYPE mkSchema();
\end{verbatim}
where the {\em RETURN-TYPE} depends on the specific database implementation. In the case
of Cassandra, the full signature is
\begin{verbatim}
static List<PreparedStatement mkSchema();
\end{verbatim}
The DUCC-component implementations inspect their schema definitions, as defined in the
IDbProperty enums in their interfaces, and create, in the case of Cassandra, a collection
of PreparedStatements which the {\em DbCreate} then uses to generate schema.
\paragraph{Utility Modules}
\begin{description}
\item[DbUtil] This contains common, static, methods that know how to manipulate
the IDbProperty enums to create schemas, indexes, convert property files into
{\em INSERT / UPDATE} statements, and so on.
\item[RmNodeState] This is example code that demonstrates one way to query the database
and generate a {\em json} object of current Resource Manager state for clients.
\item[RmQLoad] This is example code that demonstrates one way to query the database
and generate a {\em json} object of current Resource Manager demand for clients.
\end{description}
\paragraph{DUCC component-specific implementations}
These modules implement persistence for the Orchestrator, Service Manager, and Resource
Manager, implementing their indicated interfaces as well as the required {\em mkSchema}
methods. They should never be directly accessed outside of the database package. Instead,
they must be instantiated by the correct {\em Factory} as described in earlier sections.
\begin{description}
\item[HistoryManagerDb]This implements persistence for Orchestrator-generated
history and checkpoint.
\item[StateServicesDb] This implements persistence for the Service Manager's
registry and history.
\item[RmStatePersistence] This implements persistence for the Resource Manager's
dynamic state.
Note that this state is always deleted whenever RM initializes or reconfigures,
and is rebuilt as the RM itself builds or recovers its dynamic state.
\end{description}
\section{Tables}
This section describes all of the tables.
\paragraph{HistoryManagerDb} The {\em HistoryManagerDb} module is responsible for the
schema and maintenance of the tables used for most of the history objects and the
Orchestrator checkpoint.
\begin{description}
\item[job\_history] This contains the serialized objects for all Job history as {\em BLOB}s.
\item[res\_history] This contains the serialized objects for all Reservation history as {\em BLOB}s.
\item[svc\_history] This contains the serialized objects for all Service history as {\em BLOB}s.
\item[orckpt] This contains the Orchestrator checkpoint. There are two {\em BLOB}s in this object:
the current OR map, and the job-to-process map.
\item[jobs] This contains details for all jobs, extracted from the {\em BLOB}s that are written
to {\em job\_history}. It does not include any process history however.
\item[processes] This contains details for all objects that get allocated space by the RM:
job processes, service processes, AP processes.
\item[reservations] This contains details for all reservations, extracted from the {\em BLOB}s that are written
to {\em res\_history}.
\end{description}
\paragraph{StateServicesDb} The {\em StateServicesDb} module is responsible for the
service registry.
\begin{description}
\item[smreg] This contains the service registrations as submitted by users.
\item[smmeta] This contains active state of services.
\end{description}
\paragraph{RmStatePersistence} The {\em RmStatePersistence} module is responsible for all the
dynamic state produced by the RM.
\begin{description}
\item[rmnodes] This contains the state of all nodes known to the RM.
\item[rmshares] This contains details on all the shares currently allocated by RM.
\item[rmload] This contains the ``demand'' on the RM: counts of all services that are
requested by jobs, and counts of services RM is able to satisfy. (The intended purpose
of this table is to allow external agents to inspect RM load and in conjunction with
rmnodes and rmshares, determine whether RM is under-, over-, or sufficiently provisioned.)
\end{description}
\section{Scripting and Configuration}
The goal of the DUCC scripting support for the database is to make database start-up, shutdown,
schema initialization, migration, and configuration as transparent as possible.
\paragraph{Configuration} Here we define {\em configuration} to refer to the files
that define the database URL, the hosts it may be running on, the
location of the physical data, etc. A number of these values are determined
by virtue of the way DUCC and the database are designed to work together.
There are two relevant files. Pre-configured versions of these file reside in the
DUCC source base in the directory
\begin{verbatim}
src/main/resources
\end{verbatim}
During system build these files are copied into the database configuration
directory.
Note that if the database is updated or replaced it will generally be required
to obtain re-configure these files and replace them in the build directory.
The files are:
\begin{description}
\item[cassandra.yaml] This is the primary configuration file. Details of its
contents are found in the standard Cassandra documentation. We prepare this
configuration thus:
\begin{itemize}
\item Set the database {\em cluster name} to DUCC.
\item Set the hostname where the Cassandra server resides in three places:
the {\em seed\_provider}, the {\em listen\_address}, and the {\em rpc\_address}.
The reconfigured {\em cassandra.yaml} sets these all to the constant string
{\em DUCCHOST}; the DUCC startup scripting changes these to the value of
{\em ducc.head} before starting Cassandra.
\item Set the authentication scheme to {\em PasswordAuthenticator} to force
userid and password access.
\item Set the authorizer module to {\em CassandraAuthorizer} to enable specific
permissions to be set on the configured userids.
\item Set the location of the database files in {\em data\_file\_directories}.
\item Set the location of the database commitlog in {\em commitlog\_directory}.
\item Set the location of the database saved caches in {\em saved\_caches\_directory}.
\end{itemize}
\item[cassandra.env.sh] This is a shell script that is run by Cassandra as it is
starting up to detect the environment and set its internal parameters. The following
DUCC changes are applied:
\begin{itemize}
\item Alter checks for the JVM vendor so it will start with the IBM JVM.
\item Parameterize some things so the can be pulled from the environment, and
thus enable Cassandra to be customized from {\em ducc.properties}. The following
items have been modified in this file for this purpose: JMX\_PORT. (Note that
{\em Xmx and Xms} are already customizable by setting environment variables.)
\end{itemize}
\end{description}
\paragraph{Scripting}
\paragraph{}
The following updates to the DUCC scripting support the database:
\begin{description}
\item[ducc.head Configuration] When DUCC is started, a small bit of code is executed to insure
the {\em ducc.head} node is properly configured in the Cassandra configuration {\em
cassandra.yaml}. If not, a message is emitted and the configuration file is updated
before attempting to start DUCC.
\item[ducc\_util.py] This contains common database routines used by all scripts that extend
the base class {\em DuccUtil}. These methods perform these functions:
\begin{description}
\item[Enable DB] db\_configure reads {\em ducc.database.host} and if it is set to
``--disabled--'', a global variable is set to indicate the DB is disabled. Otherwise
it reads the database password from {\em ducc.private.properties} and sets that into
a global variable. If there is no password set, the DB is set disabled regardless of
the value of {\em ducc.database.host}.
\item[DB process running] db\_process\_alive attempts to determine if the database process
is running (which is not equivalent to the database being functional).
\item[DB functional] db\_alive attempts to contact the database by calling the Java bootstrap
routine ``DbAlive'' (see previous section). It returns true or false to indicate whether
the DB appears functional.
\item[DB stop] db\_stop uses the Cassandra pid to send {\em kill -TERM} to stop the DB process.
\end{description}
\item[db\_util.py] This contains database methods that can be called from any scripting that
need not extend {\em DuccUtil}. It contains methods to stop the database, update
{\em cassandra.yaml} with the value of {\em ducc.head}, and assist parsing and formatting
the results of executing {\em cqlsh}.
\item[ducc.py] This contains a method to start Cassandra. It is called from {\em start\_ducc}
and {\em startsim}.
\item[start\_ducc] This contains calls to {\em ducc\_util.py} and {\em ducc.py} to configure
and start the DB, and then insure it comes up before starting the rest of DUCC.
\item[stop\_ducc] This contains calls to {\em ducc\_util.py} to stop the DB.
\item[check\_ducc] This contains calls to {\em ducc\_util.py} to determine if the DB is running or not.
\item[ducc\_post\_install] This contains calls to {\em db\_util.py} to configure the
{\em ducc.head}, generate a random database password, and initialize the schema.
\item[db\_create] This contains methods to define a database, independently of
{\em ducc\_post\_install} intended for migration purposes.
\item[db\_loader] This contains calls to the java utility {\em DbLoader} to
migrate existing state and history files to the database.
\item[startsim] This contains calls to {\em ducc\_util.y} to start the database and
insure it starts correctly.
\item[stop\_sim] This contains calls to {\em ducc\_util.y} to stop the databse.
\end{description}