blob: 89edac52ead85ba48661031b726d5be3b40f61ac [file] [log] [blame]
\documentclass[a4paper,12pt]{article}
\usepackage{zed-cm}
\usepackage{graphicx}
%\markboth{Draft}{Draft}
\pagestyle{myheadings}
\begin{document}
\parskip 6 pt
\parindent 0 pt
\title{Cloning (v0.4)}
\author{Glyn Normington}
\maketitle
% The following three commands ensure the title page is stamped as
% confidential without a page number. Page numbering is started at the
% table of contents.
\thispagestyle{myheadings}
\pagenumbering{roman}
\setcounter{page}{0}
%=============================================================================
The Jersey release of the SpringSource dm Server will support \textit{cloning}.
This document defines cloning and explores the requirements that it must satisfy.
Refer to JIRA issue \texttt{ENGINE-825} for associated code changes.
\clearpage
\pagenumbering{roman}
\tableofcontents
% Type checking hacks
\newcommand{\true}{true}
\newcommand{\false}{false}
%\renewcommand{\empty}{\emptyset}
%=============================================================================
\clearpage
\pagenumbering{arabic}
\section{Requirements}
The requirement for cloning derives from four problems: pinning, private
statics, discarded optional imports, and fragment hosts.
\subsection{Pinning}
When an OSGi bundle is resolved, its mandatory dependencies are satisfied and any
optional dependencies that cannot be satisfied are discarded. Dependencies
are satisfied by wiring them to bundles that satisfy local constraints,
such as the version of an exported package belonging to the version range of a package import, and
which satisfy global constraints such as the \textit{uses} constraints between packages.
So the way in which a given resolved bundle is wired, that is the way in which its
dependencies are wired, depends on
the set of bundles available at the time the given bundle was resolved, the order
in which these bundles were
installed, which of these bundles were already resolved, and how those resolved
bundles were wired.
Indeed these factors may prevent a bundle from being resolved if its dependencies
cannot be met in a way which satisfies all the constraints. The most basic
reason that a bundle cannot be resolved is if its local constraints cannot be met
by existing bundles. This can be overcome by provisioning more bundles to satisfy
those dependencies.
A more subtle reason that a bundle cannot be resolved is that
its local constraints can be met by existing bundles but there is no way of satisfying
the global constraints, particularly the uses constraints. In some cases, one or more
bundles would have been capable of satisfying the local and global constraints
if only they hadn't already been wired in a particular way. These bundles are said
to be \textit{pinned}.
Note that pinning arises only from an inability to satisfy global constraints.
\subsection{Private Statics}
A resolved bundle uses a single class loader to load its classes and resources.
Each of the classes defined by the bundle's class loader has a set of zero or
more static data items.
Some scenarios require more than one set of static data items per bundle. For
example, if an application uses Log4j to do logging, then the configuration
of logging is held in Log4j statics. If more than one application was to
use Log4j, then to avoid those applications interfering with
each other's logging configuration, there would need to be a separate set
of Log4j statics per application using Log4j.
In some situations, the bundle \textit{expressing a dependency} needs to ensure there
will be a private copy of the statics of the bundle satisfying the dependency.
In other situations, the bundle \textit{providing} the dependency needs to ensure
its statics will always be provided as a private copy. But in yet other situations,
for instance ActiveMQ, the bundle providing the dependency needs to ensure
its statics will never be duplicated, for instance if it is controlling some singleton
resource such as a queue manager.
\subsection{Discarded Optional Imports}
The discussion of pinning described how optional imports are discarded during resolution.
After a bundle has been resolved, the provisioning system contents may change such
that some optional imports which were discarded
would no longer be discarded if the bundle was re-resolved.
Discarded optional imports could be re-instated by refreshing the bundle containing the imports.
However, such a refresh may be too disruptive, for instance if a large number of other bundles depend on the bundle being refreshed.
An alternative approach is to put a private copy of the bundle inside the application.
This copy can then wire its optional imports.
Fortunately, in the cases where they are significant, discarded optional imports
cause a resolution error, so it is not necessary to detect specifically when a scoped
application depends on a package corresponding to a dropped optional in a bundle
on which the application also depends.
\subsection{Fragment Hosts}
When a given bundle is resolved fragment bundles which specify the given bundle as a
host may be attached to the host.
It is not possible to attach a fragment bundle of a scoped application to a bundle in the
global scope as this would allow application content to "leak" into the global scope.
It would also be impossible to define the synthetic context of the application to cover the
fragment bundle but not its host.
Manually packaging a private copy of the host bundle inside the application solves this
problem but is poor packaging.
%=============================================================================
\clearpage
\section{Cloning}
Cloning refers to the copying of a bundle or bundles into a scope.
Cloning can apply to bundles or libraries.
Cloning automatically driven by unsatisfied uses constraints addresses pinning and discarded optional
imports.
Cloning for private statics is specified explicitly and manually in bundle manifests.
\subsection{Approach}
We concentrate on cloning of bundles and libraries as
specified in \texttt{import-bundle} and \texttt{import-library} statements and library and bundle
declarations and on auto-cloning induced by unsatisfied uses constraints.
Suppression of cloning in \texttt{import-bundle} and \texttt{import-library} statements and library and bundle
declarations is also considered.
\subsection{Divide and Conquer}
Cloning poses three separate problems: determining the bundles to clone;
creating clones and managing them subsequently; enabling Spring DM to cope with
Spring framework clones.
\subsubsection{Determining the Bundles to Clone}
Cloning may be mandated or suppressed explicitly on \texttt{import-library}, \texttt{import-bundle},
or a library or bundle declaration. The mandated cloning operations are performed first.
Then zero or more bundles are added to the cloned set incrementally
until the application resolves.
The need to re-run the deployer's metadata transformations is somewhat simplified by the
observation that the set of cloned bundles is monotonically increasing, so re-scoping never
requires de-scoping.
Finally, if we clone the Spring framework bundles used by the kernel, we need to clone the Spring DM extender and associated bundles such as ``kernel.dm''. This is discussed below.
\subsubsection{Creating and Managing Clones}
Clones can be created by copying bundles into an application's scope. This minimises
sharing but is simpler to manage subsequently than creating sets of cloned bundles shared by
more than one application.
When the original copy of a cloned bundle is refreshed, the clones must also be refreshed.
\subsubsection{Spring DM and Spring Framework Clones}
This is the topic of the next section.
Note that this problem could arise even without full cloning support if an application embedded
the Spring framework.
%=============================================================================
\clearpage
\section{Spring DM and Spring Framework Clones}
The dm Server infrastructure is separated from the clone specific application context and deals
in terms of the ModuleContext type standardised by OSGi RFC 124. This type should be fronted
by a MBean for consumption by applications and third parties which do not wish to be tied to
specific types.
\begin{figure}[h!]
\includegraphics*[scale=0.2]{cloning-v2.pdf}
\caption{Reusing Spring DM Core and Conforming to a Standard API}
\label{fig:cloningv2}
\end{figure}
The Wight release of the server extended the Spring DM application context type. Although this
is necessary to support Spring Web applications, the personality SPI should offer a set of
specific contracts, for example to allow beans to be added to an application context.
\textit{Need to consider resource loading and context class loading and whether these
have any implications for cloning.}
This design also removes a number of issues associated with the BundleStarter having to track
the creation of application contexts by the Spring DM extender by introducing a much more direct
ModuleContextFactory interface implemented by a clone specific instance.
%=============================================================================
\clearpage
\section{Z Specification}
\subsection{Introduction}
An abstract model is constructed which should be just sufficient to reason about cloning.
\subsection{Types}
Bundles are uniquely identified by bundle id.
Bundles are uniquely identified at any point in their
lifecycle by bundle symbolic name and version, although these may change if the bundle is updated.
Bundles also have content that is not modelled further here.
\begin{zed}
[BundleId, BSN, BV, BundleContent]
\end{zed}
Bundles have a bundle symbolic name, a bundle version, and some content which is not further
described.
\begin{schema}{Bundle}
bsn : BSN \\
bv : BV \\
content : BundleContent \\
\end{schema}
An OSGi framework identifies each bundle by id. Each bundle is also uniquely identified by
the combination of its bundle symbolic name and bundle version.
\begin{schema}{Framework}
bundles : BundleId \pinj Bundle \\
nv : BSN \cross BV \pinj Bundle \\
bid : BSN \cross BV \pinj BundleId \\
\where
nv = \{ b : \ran bundles @ (b.bsn, b.bv) \mapsto b \} \\
bid = bundles\inv \circ nv \\
\end{schema}
Successfully installing a bundle adds it to the bundles in the framework.
Since $bundles$ is an injection, the installed bundle's symbolic name and version must not identify another bundle in the framework.
Since $nv$ is an injection, the bundle id that identifies the installed bundle was not previously in use.
\begin{schema}{FInstall}
\Delta Framework \\
bundleData? : Bundle \\
\where
\exists id : BundleId @ bundles' = bundles \cup \{ id \mapsto bundleData? \} \\
\end{schema}
Successfully updating a bundle with a given id replaces the bundle in the framework and therefore
requires that the updated bundle's symbolic name and version do not clash with those of another
bundle in the framework.
\begin{schema}{FUpdate}
\Delta Framework \\
id? : BundleId \\
bundleData? : Bundle \\
\where
id? \in \dom bundles \\
bundles' = bundles \oplus \{ id? \mapsto bundleData? \} \\
\end{schema}
It is also possible to update a bundle with a given symbolic name and version.
Again the updated bundle's symbolic name and version must not clash with those of another
bundle in the framework.
\begin{schema}{FUpdateNV}
\Delta Framework \\
bsn? : BSN \\
bv? : BV \\
bundleData? : Bundle \\
\where
\exists id? : BundleId | id? = bid(bsn?, bv?) @ FUpdate \\
\end{schema}
Any installed bundle may be uninstalled.
\begin{schema}{FUninstall}
\Delta Framework \\
id? : BundleId \\
\where
id? \in \dom bundles \\
bundles' = \{ id? \} \ndres bundles \\
\end{schema}
%------------------------------------------------------------------------------------------------------------------------------------------
\subsection{Scope}
Informally, cloning involves copying a bundle into a scope. So we make the notion of scope
precise.
A scope is a collection of bundles, indexed by their symbolic names and versions, some of which are clones.
\begin{schema}{Scope}
snv : BSN \cross BV \pinj Bundle \\
clones : \power (BSN \cross BV) \\
\where
snv = \{ b : \ran snv @ (b.bsn, b.bv) \mapsto b \} \\
clones \subseteq \dom snv \\
\end{schema}
A bundle may be added to a scope.
\begin{schema}{AddBundle}
\Delta Scope \\
bundleData? : Bundle \\
\where
\ran snv' = \ran snv \cup \{ bundleData? \} \\
\end{schema}
\begin{zed}
Add \defs [AddBundle | clones' = clones]
\end{zed}
A clone of a bundle may also be added to a scope.
\begin{zed}
AddClone \defs [AddBundle | clones' = clones \cup \{ (bundleData?.bsn, bundleData?.bv)\} ]
\end{zed}
A bundle may be updated in a scope.
\begin{schema}{UpdateNVInScope}
\Delta Scope \\
bsn? : BSN \\
bv? : BV \\
bundleData? : Bundle \\
\where
(bsn?, bv?) \in \dom snv \\
snv' = snv \oplus \{ (bsn?, bv?) \mapsto bundleData? \} \\
clones' = clones \\
\end{schema}
Any clone of a bundle may be updated in a scope.
\begin{schema}{UpdateCloneNVInScope}
\Delta Scope \\
bsn? : BSN \\
bv? : BV \\
bundleData? : Bundle \\
\where
bsn? = bundleData?.bsn \\
bv? = bundleData?.bv \\
(bsn?, bv?) \in clones \implies snv' = snv \oplus \{ (bsn?, bv?) \mapsto bundleData? \} \\
(bsn?, bv?) \notin clones \implies snv' = snv \\
clones' = clones \\
\end{schema}
The above operation is undefined if the bundle symbolic name or bundle version are changed by the update.
In such circumstances, the implementation escalates the update for the named scope by
uninstalling and then re-installing it.
A bundle may be removed from a scope.
\begin{schema}{Remove}
\Delta Scope \\
b? : Bundle \\
\where
snv' = snv \nrres \{ b? \} \\
clones' = clones \setminus \{ snv\inv~b? \} \\
\end{schema}
%------------------------------------------------------------------------------------------------------------------------------------------
\subsection{Kernel}
In a kernel, certain scopes are named.
\begin{zed}
[ScopeName]
\end{zed}
We define a utility function for extracting the $snv$ function from a scope.
\begin{axdef}
scopesnv : Scope \fun (BSN \cross BV \pinj Bundle) \\
\where
scopesnv = (\lambda Scope @ snv) \\
\end{axdef}
A kernel has an OSGi framework, a single global scope, and a collection of named scopes.
The scopes partition the bundles in the framework.
The global scope does not have any clones.
The schema defines two components of the state in terms of the others.
$isGlobal$ is the set of bundles in the global scope.
$scopeName$ gives the name of the scope containing each bundle which is not in the global scope.
\begin{schema}{Kernel}
Framework \\
g : Scope \\
s : ScopeName \pinj Scope \\
isGlobal : \power (BSN \cross BV) \\
scopeName : BSN \cross BV \pfun ScopeName \\
\where
scopesnv \circ s \partition nv \setminus g.snv \\
g.clones = \emptyset \\
isGlobal = \dom g.snv \\
\dom scopeName = \dom nv \setminus isGlobal \\
\forall bsn : BSN; bv : BV | (bsn, bv) \in \dom scopeName @ \\
\t1 (bsn, bv) \in \dom ((scopesnv \circ s \circ scopeName)(bsn, bv)) \\
\end{schema}
We define some promotion schemas.
Some operations are directed at the global scope but may also affect named scopes.
\begin{schema}{GlobalScopeOp}
\Delta Kernel \\
\Delta Scope \\
\where
g = \theta Scope \\
g' = \theta Scope' \\
\end{schema}
Some operations directed at the global scope do not affect named scopes.
\begin{schema}{GlobalOnlyScopeOp}
GlobalScopeOp
\where
s' = s \\
\end{schema}
Some operations are directed at a specific named scope but may also affect the global scope
and other named scopes.
\begin{schema}{NamedScopeOp}
\Delta Kernel \\
\Delta Scope \\
sn? : ScopeName \\
\where
sn? \in \dom s \\
sn? \in \dom s' \\
s~sn? = \theta Scope \\
s'~sn? = \theta Scope' \\
\end{schema}
Some operations are directed at a specific named scope and do not affect the global scope
or any other named scopes.
\begin{schema}{NamedOnlyScopeOp}
\Delta Kernel \\
\Delta Scope \\
sn? : ScopeName \\
\where
sn? \in \dom s \\
g' = g \\
s~sn? = \theta Scope \\
s' = s \oplus \{ sn? \mapsto \theta Scope' \} \\
\end{schema}
\subsubsection{Installing Bundles}
A bundle may be installed in the global scope. The bundle is installed in the OSGi framework and added to
the global scope.
\begin{zed}
InstallGlobal \defs \exists \Delta Scope @ GlobalOnlyScopeOp \land FInstall \land Add \\
\end{zed}
Bundles may also be installed in a named scope.
The bundle is installed in the OSGi framework and added to the named scope.
Unfortunately, this does not have the desired scoping effect: the operation is not defined for a bundle
whose symbolic name and version clash with those of an existing bundle.
\begin{zed}
InstallInNamedScopeRaw \defs \exists \Delta Scope @ NamedOnlyScopeOp \land FInstall \land Add \\
\end{zed}
In order to prevent a bundle in a named scope from clashing with a bundle with the same symbolic name
and version in another scope, the bundle's symbolic name is transformed by prefixing it with the scope name.
\begin{axdef}
prefix : ScopeName \cross BSN \inj BSN \\
\end{axdef}
A prefixing schema is defined for convenience.
The bundle's symbolic name is prefixed with the scope name but the bundle version and content are
unchanged.
\begin{schema}{PrefixBundle}
b, pb : Bundle \\
sn? : ScopeName \\
\where
pb.bsn = prefix(sn?, b.bsn) \\
pb.bv = b.bv \\
pb.content = b.content \\
\end{schema}
$PrefixBundle$ ignores content which refers to symbolic names of bundles in the scope.
Modelling how such content is scoped would bloat the specification and add little value.
A bundle is installed in a named scope by transforming its symbolic name and then installing the transformed
bundle in the OSGi framework and adding the transformed bundle to the named scope.
\begin{zed}
InstallInNamedScope \defs (InstallInNamedScopeRaw[pb / bundleData?] \land \\
\t6 PrefixBundle[bundleData? / b]) \hide (pb) \\
\end{zed}
\subsubsection{Cloning Bundles}
Bundles may also be cloned into a named scope.
The bundle is installed in the OSGi framework and cloned into the named scope.
Again, this does not have the desired scoping effect: the operation is not defined for a bundle
whose symbolic name and version clash with those of an existing bundle.
\begin{zed}
CloneRaw \defs \exists \Delta Scope @ NamedOnlyScopeOp \land FInstall \land AddClone \\
\end{zed}
A bundle is cloned into a named scope by transforming its symbolic name and then installing the transformed
bundle in the OSGi framework and cloning the transformed bundle into the named scope.
\begin{zed}
Clone \defs (CloneRaw[pb / bundleData?] \land \\
\t6 PrefixBundle[bundleData? / b]) \hide (pb) \\
\end{zed}
\subsubsection{Updating Bundles}
A bundle in the global scope may be updated. Any clones of the bundle are also updated.
\begin{schema}{UpdateGbl}
GlobalScopeOp \\
FUpdate \\
UpdateNVInScope \\
\where
bsn? = (bundles~id?).bsn \\
bv? = (bundles~id?).bv \\
(bsn?, bv?) \in isGlobal \\
\dom s' = \dom s \\
\forall sn? : \dom s @ \\
\t1 NamedScopeOp \land \\
\t1 (\exists pb : Bundle @ \\
\t2 UpdateCloneNVInScope[pb / bundleData?] \land \\
\t2 PrefixBundle[bundleData? / b]) \\
\end{schema}
\begin{zed}
UpdateGlobal \defs \exists \Delta Scope; bsn? : BSN; bv? : BV @ UpdateGbl \\
\end{zed}
A bundle in a named scope is updated by updating it in the OSGi framework and
in the named scope.
\begin{schema}{UpdateInNS}
NamedOnlyScopeOp \\
FUpdate \\
UpdateNVInScope \\
\where
bsn? = (bundles~id?).bsn \\
bv? = (bundles~id?).bv \\
(bsn?, bv?) \notin isGlobal \\
sn? = scopeName(bsn?, bv?) \\
\end{schema}
\begin{zed}
UpdateInNamedScope \defs \exists \Delta Scope; bsn? : BSN; bv? : BV; sn? : ScopeName @ UpdateInNS \\
\end{zed}
The update operation need not distinguish between the global and named scope cases.
\begin{zed}
Update \defs UpdateGlobal \lor UpdateInNamedScope \\
\end{zed}
There is a corresponding update operation which takes a symbolic name and version.
\begin{schema}{UpdateNV}
\Delta Kernel \\
bsn? : BSN \\
bv? : BV \\
bundleData? : Bundle \\
\where
\exists id? : BundleId | id? = bid(bsn?, bv?) @ Update \\
\end{schema}
\subsubsection{Uninstalling Bundles}
A bundle is uninstalled from the global scope by uninstalling it from the OSGi framework and
removing it from the global scope.
\begin{schema}{UninstallGbl}
GlobalOnlyScopeOp \\
FUninstall \\
Remove \\
\where
b? = bundles~id? \\
\end{schema}
\begin{zed}
UninstallGlobal \defs \exists \Delta Scope; b? : Bundle @ UninstallGbl \\
\end{zed}
Note that the above operation does not uninstall any clones of the bundle being uninstalled.
A bundle is uninstalled from a named scope by uninstalling it from the OSGi framework and
removing it from the named scope.
\begin{schema}{UninstallFromNS}
NamedOnlyScopeOp \\
FUninstall \\
Remove \\
\where
b? = bundles~id? \\
\end{schema}
\begin{zed}
UninstallFromNamedScope \defs \exists \Delta Scope; b? : Bundle @ UninstallFromNS \\
\end{zed}
Note that the above operation \textit{can} be used to uninstall a cloned bundle.
The uninstall operation need not distinguish between the global and named scope cases.
\begin{zed}
Uninstall \defs UninstallGlobal \lor UninstallFromNamedScope \\
\end{zed}
%=============================================================================
\clearpage
\section{Alternative Designs}
Figures \ref{fig:cloningv3} and \ref{fig:cloningv1} show alternative designs which were considered
and rejected.
Both designs reproduce the Spring DM extender inside each clone which causes complications for
listening.
Cloned extenders need to ignore events outside their `scope' and the non-cloned extender
needs to ignore events associated with clones.
So it seems preferable to initiate the building of application contexts at the level of deploying a bundle and delegate building the application context to a clone-specific standard delegate.
Figure \ref{fig:cloningv3} forces much kernel and server code to be cloned with corresponding
overhead. This makes it much more difficult to integrate with dm Server wide infrastructure such as
management and third party code as this also would need cloning.
In the limit, this design clones most of the kernel and server and would be better implemented by
hosting each clone in its own server instance.
Although it should be possible to host each clone on its own server instance, this should not be forced
on the users.
Especially during development, it is convenient to deploy multiple versions of applications and the
libraries they use concurrently.
Figure \ref{fig:cloningv1} on the other hand requires a complex mapping of cloned to standard
types. The whole mapping is somewhat wasteful given that there is no need to generate cloned
events in the first place (except perhaps to support legacy Spring DM applications).
\begin{figure}[h!]
\includegraphics*[scale=0.2]{cloning-v3.pdf}
\caption{Reusing All of Spring DM and Cloning the Kernel}
\label{fig:cloningv3}
\end{figure}
\begin{figure}[h!]
\includegraphics*[scale=0.2]{cloning-v1.pdf}
\caption{Reusing All of Spring DM and Adapting to the Kernel}
\label{fig:cloningv1}
\end{figure}
\end{document}