Dynamic Reconfiguration Subsystem (DRSS)

Quick Start User’s Guide

 

Introduction

 

Dynamic reconfiguration refers to the process of applying software modifications at runtime without disrupting the functions performed by the running system.  In a component-oriented environment, runtime reconfiguration is achieved by dynamically modifying or extending the behavior of the components which implement the system without disrupting the services supplied by those components. 

 

Stated informally, software interception is the transparent interposition of code between the interface of a component and the component implementation.  The component encapsulating the interposed code is termed an interceptor, and the component whose calls are being intercepted is referred to as a target.  By adding or removing the interceptors associated with a particular target, the target’s behavior can be changed dynamically, in a way that is transparent to clients of the target.

 

The Dynamic Reconfiguration Subsystem (DRSS, pronounced Doris) is a highly dynamic interceptor architecture developed using Microsoft .NET.  DRSS enables the development of adaptable long-running distributed systems by providing runtime management of the interceptors applied to a running system.

 

For example, a remote method invocation might be subject to snooping by a newly discovered intruder.  A solution is to dynamically deploy interceptors at the client and at the server to encrypt invocation requests and responses, as shown in the illustration below.  The top part of the figure shows the messages involved in a server method call for a client. The bottom part shows the messages with encrypting and decrypting interceptors added.

 

 

 

Other applications for interceptors include fault tolerance (for example, introducing triple modular redundancy by replicating server components), quality-of-service (for example, prioritizing method invocations), and performance profiling.

 

DRSS Development Quick Start Guide

 

Application Development

 

Platform is the primary class of interest to DRSS application developers.  The class serves as a container for three special types of objects: interceptor chains, targets, and proxies.  An interceptor is a unit of behavior to be applied before or after a method invocation.  An interceptor chain is a composition of interceptors in sequence – a number of behaviors – to be applied before or after a method invocation.  A target is an object requiring interception services.  And finally, a proxy is an object used to invoke methods on a target.  The proxy implements the same interface(s) as the corresponding target, and from the client programmer’s perspective, appears as the target itself.

 

These objects are managed by two different registries, each of which is exposed as a public property of Platform.  The first, InterceptorChainRegistry, provides management methods for creating and destroying  interceptor chains, as well as adding and removing interceptors from those chains.  The second, StubSkeletonRegistry, provides management methods for registering and unregistering targets, as well as creating proxies to interact with registered targets.  (Note that the local platform is responsible for creating proxies to communicate with targets registered on the local platform, as well as targets registered on remote platforms.)  Each registry additionally provides methods for searching for and enumerating registered objects.

 

Instantiating the local platform is typically the first task in any DRSS application:

 

Platform plaLocalPlatform = new Platform(50001);

 

All objects registered with the platform are exposed to remote processes via TCP/IP.  The integer argument to the constructor identifies the port number on which the platform should listen for connection requests from remote platforms.  Note that the constructor is overloaded.  An alternate version of the constructor allows the client to specify various TCP/IP timeouts.

 

Every object requiring interception services must register with a Platform instance.  When registering an interception target, the registration methods allow the client to specify which interceptor chains to associate with the target.  Every target is associated with exactly two interceptor chains: a send-type interceptor chain that holds interceptors derived from SendInterceptor, and a receive-type interceptor chain that holds interceptors derived from ReceiveInterceptor.  Send-type interceptors are activated when an invoker requests a method invocation, but before the request is received by the target, as well as when a target sends a result, but before the result is received by the invoker.  Analogously, receive-type interceptors are activated when a target receives a request, but before the request is serviced, as well as when an invoker receives a response, but before the response is handled.

 

The platform provides one default send-type interceptor chain, and one default receive-type interceptor chain.  If the default chains are insufficient, the interceptor chain registry provides methods for creating new interceptor chains:

 

Guid guiSIChain

= plaLocalPlatform.InterceptorChainRegistry.RegisterSIChain

("SI Chain", "A sample SI chain", null);

 

Guid guiRIChain

= plaLocalPlatform.InterceptorChainRegistry.RegisterRIChain

("RI Chain", "A sample RI chain", null);

 

The first argument to each method is the name to associate with the newly created chain.  The interceptor registry uses this value when performing name-based lookup on interceptor chains.  The second is some informative description of the chain for query purposes, and the final argument is an optional object to associate with the chain.  (This object might be used, for example, to hold the evolving specification of the interceptor chain.)  The Guid objects returned represent handles to the newly created chains, and are used when interacting with the interceptor chain registry to refer to the underlying chains.

 

Consider an instance of the ImageData class from the demo application included with the initial release of DRSS:

 

ImageData idImageData = new ImageData(bmpImage);

 

If this object requires interception services, it must be registered with a platform instance as an active object:

 

Guid guiTargetID =

plaLocalPlatform.StubSkeletonRegistry.RegisterActiveObject

("idImageData", "An instance of ImageData", null,

 guiSIChain, guiRIChain, idImageData);

 

Like the chain registration methods, the first argument is the name to associate with the interceptor target, the second is an informative description of the target, and the third is an optional object to associate with the target.  The fourth argument is the handle corresponding to the send-type chain to attach to the target, and the fifth is the handle corresponding to the receive-type chain to attach to the target.  The final argument is a reference to the target itself.  Like the chain registration methods, the Guid returned represents a handle to the newly registered interception target.  Note that the registration methods are overloaded to provide a number of different registration alternatives.  There are methods, for example, which omit the chain ID arguments, indicating that the default chains should be used.

 

Once a target is registered, clients must interact with the target by means of a platform-generated proxy.  Whether the client is local or remote to the target, it must not interact with the target object directly.  To construct a proxy for a target, the client must specify the address of the registered target object.  The address, represented by PlatformHostedObjectAddress, consists of the IP end-point of the target’s platform, and the target’s Guid.  This IP / Guid pair is constructed as follows:

  


 

 

PlatformHostedObjectAddress phoTargetAddress =

new PlatformHostedObjectAddress

(plaLocalPlatform.LocalEndPoint, guiTargetID);

 

Once the target’s address has been determined, the proxy can be constructed as follows:

 

idImageData = (ImageData)

plaLocalPlatform.StubSkeletonRegistry.GetActiveObjectProxy

(phoTargetAddress, typeof(ImageData), guiSIChain, guiRIChain);

 

The first argument specifies the address of the target, and the second indicates the type of interface to be provided by the generated proxy.  (In most cases this should be the same type as the target.)  Note that like targets, proxy objects can take advantage of interception services.  The third argument is a handle corresponding to the send-type chain to attach to the proxy, and the fourth is a handle corresponding to the receive-type chain to attach to the proxy.  The proxy creation methods are overloaded to provide a number of different creation alternatives.  There are methods, for example, which omit the chain ID arguments, indicating that the default chains should be used.

 

The object returned by GetActiveObjectProxy() is used in exactly the same way as a traditional ImageData instance.  From the client’s perspective, this instance should be thought of as the target object itself.  With the exception of object creation and registration, which can easily be encapsulated using the Abstract Factory Pattern, the interception infrastructure is transparent to both clients and servers.

 

Reconfiguration

 

Every platform treats its registries as interceptor targets, exposing the registries via TCP/IP.  Unlike standard targets, however, the Guids associated with each registry never change.  That is, regardless of the containing platform instance, the Guid constant PlatformInterceptorChainRegistry.IC_REGISTRY can always be used to refer to the interceptor chain registry used by the platform at a particular IP endpoint.  Similarly, PlatformStubSkeletonRegistry.SS_REGISTRY can always be used to refer to the proxy (stub) and target (skeleton) registry used by the platform at a particular IP endpoint.  Exposing these registries via TCP/IP enables remote platform management.  Using well-known Guids to refer to the registries greatly simplifies the bootstrapping process when initiating distributed collaborations.

 

Consider, for example, a remote client that interacts with the ImageData instance registered in the above example.  For debugging purposes, the remote client might wish to temporarily install debugging interceptors into the chains associated with the ImageData instance.  Interceptor management methods are provided by the interceptor chain registry of the platform hosting the ImageData instance.  As with any registered target, the client must construct a proxy by specifying the address of the remote object:


 

 

PlatformHostedObjectAddress phoRegistryAddress =

new PlatformHostedObjectAddress

(ipeRemoteEndPoint,   PlatformInterceptorChainRegistry.IC_REGISTRY_ID);

 

The interceptor chain registry proxy is then constructed as follows:

 

PlatformInterceptorChainRegistry

picRemoteRegistry = (PlatformInterceptorChainRegistry)

plaLocalPlatform.StubSkeletonRegistry.GetActiveObjectProxy

(phoRemoteAddress, typeof(PlatformInterceptorChainRegistry));

 

Note that because no chain identifiers were supplied to GetActiveObjectProxy(), the default chains will be attached to the generated proxy.  As before, the proxy can now be used by the client as though it were a reference to the target itself.  In particular, the remote client can use the proxy to install a debugging interceptor in each chain associated with the ImageData instance:

 

picRemoteRegistry.RegisterSendInterceptor

(guiSIChainID, 0, typeof(DebugLoggingSI));

 

picRemoteRegistry.RegisterReceiveInterceptor

(guiRIChainID, 0, typeof(DebugLoggingRI));

 

The first argument to each method is a handle corresponding to the interceptor chain to be affected.  (In this example the Guids respectively refer to the send-type and receive-type chains associated with the remote ImageData instance.)  The second argument indicates the position within the chain at which the new interceptor should be inserted.  Finally, the third argument indicates the type of interceptor to construct.  Note that these methods are overloaded; several variations are provided.  The particular versions of the methods used here specify the type of interceptor to construct, rather than an explicit interceptor instance.  This allows interceptors that are not tagged as serializable to be installed remotely.  The stub skeleton registry offers analogous versions of these methods to enable the remote registration of targets that are not tagged as serializable.

 

After the remote client has finished interacting with the ImageData instance, it may wish to uninstall the interceptors that were registered in the previous step:

 

picRemoteRegistry.UnregisterSendInterceptor(guiSIChainID, 0);

 

picRemoteRegistry.UnregisterReceiveInterceptor(guiRIChainID, 0);

 

Like the interceptor registration methods, the first argument to each method is a handle corresponding to the interceptor chain to be affected, and the second argument indicates the position of the interceptor that should be removed.  Assuming the absence of other clients, both interceptor chains should now be empty.

 


 

Interceptor Development

 

To better understand interception in DRSS, it is helpful to think of object interactions in terms of the Actor model of computation.  Components interact by means of message passing; no direct invocations are permitted.  When a message is received by an actor, the actor is allowed to take action on its internal state, as well as send messages to other actors.  The analogy to traditional object-oriented systems is fairly straightforward.  When an invoker invokes a method on a target, the invoker can be thought of as having sent a request message to the target requesting a method invocation.  When the target receives the message, it performs the requested operation, and can be thought of as having sent a response message back to the invoker, notifying the invoker of the invocation results.  Finally, the invoker receives the response message, and the calling sequence is complete.

 

As discussed previously, DRSS provides support for two types of interceptor objects.  Send-type interceptors, which must derive from the SendInterceptor base class, are responsible for intercepting messages just before they are sent.  Analogously, receive-type interceptors, which must derive from the ReceiveInterceptor base class, are responsible for intercepting messages just after they have been received.  As send-type interceptors share a common interface, they can be composed in sequence, where the messages being intercepted are passed to each interceptor in turn.  These send-type interceptor sequences are represented as instances of SendInterceptorChain.  Receive-type interceptor sequences are defined analogously, and are represented as instances of ReceiveInterceptorChain.  Upon registration with the platform, each object instance is associated with one SendInterceptorChain, and one ReceiveInterceptorChain.  Dynamic reconfiguration is achieved by inserting and removing interceptor objects from the various interceptor chains.

 

Implementing a send-type interceptor involves creating a new derived class of SendInterceptor.  The derived class designer provides the interception code by overriding the base class definition of InterceptMessage():

 

public abstract class SendInterceptor : …

{

public abstract void InterceptMessage(..., ref Message msg);

}

 

The final parameter to InterceptMessage(), msg, refers to the message being intercepted,   and can be set to null to indicate that the message should not be propagated to other interceptors in the chain.

 

Likewise, implementing a receive-type interceptor involves creating a new derived class of ReceiveInterceptor, and overriding the base class definition of InterceptMessage():


 

 

public abstract class ReceiveInterceptor : …

{

      …

public abstract void InterceptMessage(ref Message msg);

}

 

Again, msg refers to the message being intercepted, and can be set to null to prevent the message from propagating to other interceptors in the chain. 

 

Message content is maintained on a stack, and is exposed as Content, a public property of Message.  In the absence of content-modifying interceptors, the content stack will always contain exactly one entry.  If the message represents an invocation request, the content entry will be of type MethodCallInformation.  If the message represents an invocation response, the content entry will be of type MethodResultInformation.  The reader is referred to the interface definitions for complete details.

 

As an example, consider a simple send-type interceptor that logs messages to Visual Studio’s output window:

 

public override void InterceptMessage(, ref Message msg)

{

System.Diagnostics.Debug.WriteLine

(msg.MessageContent.Peek().ToString());  

}