Jump to: navigation, search

Aperi Connector Design R2

Revision as of 23:54, 18 September 2006 by Slupesky.us.ibm.com (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Introduction

The purpose of the connector component is to handle the remote invocation and execution of Aperi services. For a server, the connector will publish local services, deserializing & routing incoming requests, and serialize outgoing responses. For the client, the connector will bind to remote services, serialize outgoing requests, and deserialize incoming responses.

This connector could be adopted by all parts of the Aperi storage management system (server, host agents, gui, etc).

Design Assumptions

Security - Basic SSL is supported; however, support for certificate management is not handled in this design. Once a valid certificate and keystore are in place, the connector will support SSL; obtaining the certificate and keystore are not assumed.

Design Rationale

The connector leverages legacy code whenever it makes sense. For example, the initial wire format is xml over rpc. If this component was being built from scratch, we might choose something else. For now, however, using this legacy system makes for more rapid development and adoption of this component.

Designs Considered

  • Architecture - approaches are consistent with the direction of the architecture
  • Device Server - designed for consumption by the device server
  • Agent - designed for consumption by the host agent

Open Items

  • Test cases have not been defined
  • Messages need additions

Requirements Addressed and Features Provided

Functional & Technical Requirements

The following requirements are 'must do' requirements. The component cannot be considered complete if any of these requirements are not provided.

Requirements

  • Wire Format (web services, xml rpc, jmx, etc)
    • The connector should abstract the underlying wire format from its customers (local services). Whether the connector leverages web services, xml rpc, or jmx, it should only have a minimal impact to the user.
  • Security
    • The connector must handle authentication & secrecy
    • SSL should be the authentication & encryption method of choice
    • The connector must be able to operate in a secure or unsecured mode
  • Publishing Remote Services
    • Services should be easily published, not requiring additional class dependencies by the connector
    • By registering an SMF service with a particular set of properties, the service should be designated "remotable" by the connector

Features

The connector is designed to implement the following features:

Remote Procedure Calls

The connector's basic function is to create and process remote procedure calls (RPC).

Raw Socket support

The connector handles a remote xml rpc call. It also allows for raw socket communication between two parties. This ability to handle rpc or other proprietary protocols (via a raw socket) through the same port and connection mechanism is considered a to be a valuable feature. This will allow for the data subagent (which uses a proprietary serialization approach) and the fabric subagent (xml rpc) to receive incoming messages through the same port.

Optional Authentication & Encryption

Standard SSL will be leveraged to provide authentication and encryption. SSL leverages public key cryptography. For more information on SSL, see this primer.

Architectural Components

The connector is composed of three main architectural pieces:

  • Custom Connector code (org.eclipse.aperi.connector)
  • Apache SOAP (org.apache.soap)
  • Jetty Servlet Container & Equinox Http Service

Custom Connector Code

The image below provides a pictoral view of the pieces of custom connector code. The plug-in name is org.eclipse.aperi.connector.

AperiConnectorFiles.png

Source Packages

org.eclipse.aperi.connector contains code that is exposed externally. The Constants class contains property keys needed to describe published services. The ConnectorClient provides static methods used for binding to remote or local services.

org.eclipse.aperi.connector.internal contains code common to the connector, but not to be exposed externally. The "internal" portion of the package conforms to the Eclipse paradigm for classes that should not be exposed to other plug-ins. In this package is the Activator. It is responsible for launching the Jetty servlet container and the OSGi Http Service (implemented by Eclipse). The MappingRegistry is responsible for obtaining the correct serializer or deserializer during SOAP processing.

org.eclipse.aperi.connector.internal.client contains the implementation of the client code. The client code is not dependent on a servlet container or an http service, but is dependent on SOAP.

org.eclipse.aperi.connector.internal.server contains the server side implementation. The Connector class is a servlet (extending the SOAP RPCRouterServlet) and an OSGi service listener. It obtains references to all OSGi services that desire to be published as SOAP services. This reference is then registered to with the SOAP service registry, where incoming calls are unmarshalled and routed. The BiModalSocketListener and BiModalHttpConnection classes are children of Jetty classes that implement slight modifications to support raw socket handoff functionality. The ServletActivator replaces the Activator found in org.eclipse.equinox.jetty.http plug-in (which is no longer needed). This activator starts the servlet container in a way that leverages the BiModal* classes.

Extension Points

Three extension points are defined by their .exsd files:

  • serializer - for adding new serializers
  • deserializers - for adding new deserializers
  • socket handler - for adding handlers for raw socket requests

Apache SOAP (XML RPC)

The connector leverages Apache SOAP to provide server-side infrastructure for deploying, managing and running SOAP enabled services. It also provides a client-side API for invoking SOAP services. Apache SOAP is the predecessor to the Apache Axis project. Axis should be considered for future development, but continued use of the legacy SOAP package minimizes risk and development time.

Handling Incoming Calls

Incoming calls enter the RPCRouterServlet. To process the request, it first queries its internal ServiceManager to verify that the requested service exists. Then the RPCRouter processes the incoming xml to extract the call and parameters. Finally, the RPCProvider uses reflection to invoke the call against the requested service. The results are serialized and returned to the remote caller as a SOAP response.

org.eclipse.aperi.connector.internal.server.Connector extends org.apache.soap.server.http.RPCRouterServlet. Incoming messages (handled by doPost()) are sent directly to the parent class.

Upon startup, the Connector queries the OSGi Service Registry in search of services that desire to be published remotely. The Connector also acts as an OSGi service listener. When services are registered with the OSGi service registry, the Connector is notified. The service's properties are queried. If a web service name is provided, the Connector concludes that this service desires to be published. A reference to the service is obtained and stored in the ServletContext. This context is queried during an incoming request to obtain the service reference and invoke the appropriate call. This bit of logic ties together the OSGi service registry to the SOAP service manager without creating duplicate instances of the service. In other words, the SOAP service manager does not instantiate or load the actual service implementation; instead, it locates a reference directly from the OSGi service registry.

Serialization

Apache SOAP supports standard serialization as specified by SOAPv1.1 encoding. For more, see the Apache SOAP project page. By default serialization of common types, including Java Beans, is supported. The Connector defines an extension point to support the addition of custom serializers and deserializers for non-supported types. Extensions contain a Java object that must implement either a org.apache.soap.util.xml.Serializer (or Deserializer). The Mapping Registry will consult the Extensions Registry when (de)serialization is needed for non-supported type. [note for Todd: integrate at the query(De)Serializer() method).

Note: The mapping registry contains a cache of all serializers and deserializers. The soap package unfortunately instantiates a new SOAPMappingRegistry for every deployment descriptor (every service deployed). To overcome this blunder, we will implement a lightweight wrapper to proxy calls to a singleton instance of the actual mapping registry. Only the wrapper will be have multiple instances (one per service); it will not contain any instance variables.

Exception Handling

When a remote call fails, Soap Faults are thrown in accordance to SOAPv1.1. Therefore, basic exceptions are thrown remotely and reconstructed on the receiving end just like other objects. For non-supported exceptions, custom serializers and deserializers should be used.

Equinox Jetty Servlet Container

The connector leverages the Equinox Jetty Servlet Container & OSGi HTTP Service for handling incoming calls. As a standalone component, four plug-ins are leveraged to start the OSGi Http Service and servlet container:

  • org.eclipse.osgi.services - contains the Http Service interface definition
  • org.eclipse.equinox.servlet.api - contains the java servlet interfaces
  • org.eclipse.equinox.servlet.bridge.http - the equinox implementation of the Http Service. A servlet named org.eclipse.equinox.servlet.bridge.http.internal.ProxyServlet, when deployed into a servlet container, registers itself as provider of the OSGi-defined Http Service. This plugin contains the servlet-based Http Service, but it does not provide the servlet container needed to run servlets like itself. When this plug-in is started, essentially nothing happens. Not until a servlet container is deployed is the Proxy Servlet started and the Http Service implementation launched.
  • org.eclipse.equinox.jetty - Jetty's version of a servlet container. This implementation starts a threaded server that leverages Java's Server Sockets. Incoming requests are handled by pooled threads; http is handled, and calls are then routed to the appropriate servlet. This plug-in handles secure, SSL requests also. This plugin does not contain an Activator, but depends on a separate plug-in for initialization.
  • org.eclipse.equinox.jetty.http - This plug-in only contains one class, the Activator. This activator is responsible for starting org.eclipse.equinox.jetty and deploying the Proxy Servlet found in org.eclipse.equinox.servlet.bridge.http (which in-turn starts the OSGi Http Service).

Integrating the Connector with the Jetty Servlet Container

The server-side of the connector, the org.eclipe.aperi.internal.server.Connector, is simply a servlet that, once deployed, can handle xml rpc calls (via its integration with Apache SOAP). However, to implement raw socket support, deeper integration with Jetty is required.

Instead of using the Jetty SocketListener, the connector will leverage its own BiModalSocketListener (a child or the Jetty class) to obtain access to the socket when needed. This is the integration point with Jetty for obtaining a socket for the connector raw socket support.

Component Packaging & Dependencies

This component will be packaged as the org.eclipse.aperi.connector. It is responsible for managing the network I/O. It depends on the presence of a servlet container and a rpc package - in our case, soap for xml rpc. In both cases, the connector is intimately dependent on the Jetty servlet container; using just "any old" servlet container will not work. Bimodal (raw socket) support requires intimate integration with the servlet container. Therefore, this implementation is tied specifically to the jetty servlet implementation. Additionally, the rpc mechanism is also tied to the apache soap implementation. In other words, the connector's dependencies are not easily "swapable".

There are a number of approaches to consider for the packaging of this plug-in.

Option 1 - All inclusive This option places embeds the all servlet plug-ins and soap plug-ins in the connector. Meaning, the connector will contain the jetty servlet jars and the apache soap jars; these jars will live in the connector and will not be deployed as separate plug-ins. This approach reduces "plug-in clutter" in that fewer plug-ins need to be deployed; all network I/O will be all-inclusive in the connector. One drawback is that the connector bundle would be rather large; if deployed into a component that only requires client-side usage (like the gui), the presence of servlet code inside the connector proves to be an unnecesary weight.

Option 2 - Connector includes Soap Implementation In this scenario, the connector plug-in includes the soap.jar package, eliminating the need for a separate soap plug-in. One plus with this approach is that components like the GUI (without a server) can leverage the connector in a more efficient manner - no unnecessary code included. The servlet container, jetty, will remain separate in its own plug-in. But what should be done with org.eclipse.equinox.jetty.http? It only contains the Activator used to start the servlet container - this is not needed because the connector starts the servlet container using its own Activator (this is a necessary part of integration for the bimodal functionality). So this option proposes that the org.eclipse.equinox.jetty.http plug-in not be included in the Aperi distribution. By doing this, we remove unused and unnecessary plug-ins. At the same time, however, we only deploy a subset of the plug-ins distributed with the Jetty package.

Option 3 - Connector, Soap, and Jetty deployed separately In this scenario, the connector would not include the soap implementation or the Jetty servlet implementation.

For this implementation, the connector will leverage option 2. This option provides the most efficiency while maintaining the necessary amount of flexibility.

Manifest File

The manifest file is posted below. Although the org.eclipse.equinox_jetty is needed for the server-side piece of the connector, it is important that this is NOT listed as a required bundle so that the connector has the flexibility of being deployed in a client-side context only.

Manifest File

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Connector Plug-in
Bundle-SymbolicName: org.eclipse.aperi.connector;singleton:=true
Bundle-Version: 1.0.0
Bundle-Activator: org.eclipse.aperi.connector.internal.Activator
Bundle-Localization: plugin
Require-Bundle: org.eclipse.aperi.common,
 org.eclipse.aperi.log
Export-Package: org.eclipse.aperi.connector
Import-Package: javax.servlet;version="2.4.0",
 javax.servlet.http;version="2.4.0",
 org.eclipse.equinox.servlet.bridge.http.internal,
 org.mortbay.http,
 org.mortbay.jetty.servlet,
 org.mortbay.util,
 org.osgi.framework;version="1.3.0",
 org.osgi.service.http;version="1.2.0"
Bundle-ClassPath: soap.jar,
 .

This component, org.eclipse.aperi.connector, depends on:

  • org.eclipse.aperi.common
  • org.eclipse.aperi.log
  • org.eclipse.equinox.jetty

Cross-Component Flows (Usage)

Publishing a Service

The connector allows an OGSi service to be published remotely. The following information is needed for the connector to publish the service:

  1. Service Name - the name of the service interface
  2. Instance Name - the name of the service instance

When an OSGi service is registered, the above information is provided. When the connector finds a service with a the instance name in its properties, it automatically publishes the service as remotable. Below is an example of registering a service with OSGi in such a way to be published by the connector:

       //Deploy Sample Service
      ISampleService iss = new SampleService();
      Dictionary<String, String> props = new Hashtable<String, String>();
      props.put(Constants.WEB_SERVICE_NAME, ISampleService.class.getName());
      ctx.registerService(ISampleService.class.getName(), iss, props);

Binding to a Remote Service

The connector allows for clients to either obtain a proxy to a remote service or to obtain a socket to a remote service. Below is an example of how other components will leverage the connector.

//Deploy Sample Service
ISampleService iss = ConnectorClient.getService(localhost, 9000, ISampleService.class, null);
Socket socket = ConnectorClient.getSocket(localhost, 9000, "serviceName");

External Interfaces and Semantics

Programmatic Interfaces (APIs)

ConnectorClient.java

/**
	 * @param host - the host publishing the service; if null, a local service
	 * is assumed
	 * @param port - the port of the listening host
	 * @param serviceClass - the interface class of the requested service
	 * @return - returns a proxy object that implements the requested service
	 * @throws ConnectorException - thrown when an exception occurs
	 */
	public static Object getService(String host, int port, 
					Class serviceClass) 
	throws ConnectorException;

        /**
         * The host target name is obtained from the system property: 
         *         -Dconnector.remote.host
         * The host port is obtained from the system property:
         *         -Dconnector.remote.port
	 * @param serviceClass - the interface class of the requested service
	 * @return - returns a proxy object that implements the requested service
	 * @throws ConnectorException - thrown when an exception occurs
	 */
	public static Object getService(Class serviceClass) 
	throws ConnectorException;

	/**
	 * Once the socket is obtained, the responsibility to manage resources (and to close
	 * the connection) belongs to the client.
	 * @param host - the location of the service; if null, localhost is assumed
	 * @param port - the port of the desired service
	 * @param serviceName - the name of the service desired
	 * @return - a socket connection to the desired service; follow-on communication
	 * may occur through the socket
	 * @throws ConnectorException - an exception is thrown when an error occurs in
	 *  obtaining the socket
	 */
	public static Socket getSocket(String host, int port, String serviceName)
	throws ConnectorException;

        /**
         * The host target name is obtained from the system property: 
         *         -Dconnector.remote.host
         * The host port is obtained from the system property:
         *         -Dconnector.remote.port
         * @param serviceName - the name of the service desired
	 * @return - a socket connection to the desired service; follow-on communication
	 * may occur through the socket
	 * @throws ConnectorException - an exception is thrown when an error occurs in
	 *  obtaining the socket
	 */
	public static Socket getSocket(String serviceName)
	throws ConnectorException;

Constants.java

public interface Constants {
	static final String CONTEXT_PATH = "/ServiceManager";
	static final String WEB_SERVICE_NAME = "org.eclipse.aperi.webservice.name";
	static final String HTTP_PROTOCOL = "http://";
	static final String HTTPS_PROTOCOL = "https://";
}

SocketHandler.java


public interface SocketHandler {
	void handleSocket(Socket s) throws Throwable;
}

Excerpt from serializer.exsd


<element name="serializer">
      <complexType>
         <attribute name="encodingStyleURI" type="string" use="required">
            <annotation>
               <documentation>
                  
               </documentation>
            </annotation>
         </attribute>
         <attribute name="javaClassType" type="string" use="required">
            <annotation>
               <documentation>
                  
               </documentation>
            </annotation>
         </attribute>
         <attribute name="impl" type="string" use="required">
            <annotation>
               <documentation>
                  Using this extension point, custom serializers can be deployed to handle non-support Java types.
               </documentation>
               <appInfo>
                  <meta.attribute kind="java" basedOn="org.apache.soap.util.xml.Serializer"/>
               </appInfo>
            </annotation>
         </attribute>
      </complexType>
   </element>

Excerpt from deserializer.exsd


<element name="deserializer">
      <complexType>
         <attribute name="namespaceURI" type="string" use="required">
            <annotation>
               <documentation>
                  
               </documentation>
            </annotation>
         </attribute>
         <attribute name="localPart" type="string" use="required">
            <annotation>
               <documentation>
                  
               </documentation>
            </annotation>
         </attribute>
         <attribute name="encodingStyleURI" type="string" use="required">
            <annotation>
               <documentation>
                  
               </documentation>
            </annotation>
         </attribute>
         <attribute name="impl" type="string" use="required">
            <annotation>
               <documentation>
                  
               </documentation>
               <appInfo>
                  <meta.attribute kind="java" basedOn="org.apache.soap.util.xml.Deserializer"/>
               </appInfo>
            </annotation>
         </attribute>
      </complexType>
   </element>

Excerpt from socketHandler.exsd


<element name="socketHandler">
      <complexType>
         <attribute name="impl" type="string" use="required">
            <annotation>
               <documentation>
                  
               </documentation>
               <appInfo>
                  <meta.attribute kind="java" basedOn="org.eclipse.aperi.connector.SocketHandler"/>
               </appInfo>
            </annotation>
         </attribute>
      </complexType>
   </element>

External Messages

The following represent the messages that will be used by the Connector. Message keys will be provided later:

  • Info - The servlet container has successfully started on port ? and host ?
  • Info - The Http Service has successfully deployed into the servlet container.
  • Info - The service ? has been published as a SOAP service.
  • Info - Secure mode requested, connector starting an https listener.
  • Error - Unable to find keystore ?
  • Info - Client authentication requested for all secure, incoming requests
  • Error - Client certificate, ?, not valid.
  • Error - Cannot service request, service ? can not be found
  • Error - Unable to serialize java type ?. Add serializer for this type
  • Error - Unable to deserialize java type ?. Add deserializer for this type
  • Error - Unable to find SocketHandler, ?. Deploy socket handler extension
  • Error - Failed to decrypt password! Please fix password.
  • Error - Unable to process SSL algorithm, ?. Please choose a different algorithm
  • Error - Unable to load property ?, loading default property value of ?

Security and Authentication

Encryption/Cryptography

When security is enabled, encryption is handled through SSL. Algorithms can be selected in the Connector configuration.

Authentication

When security is enabled, authentication is handled via SSL.

Authorization

The authorization mechanism does not change. Legacy authorization code should be leveraged. This code is currently found in org.eclipse.aperi.common. However, this code is expected to find its way to org.eclipse.aperi.auth. This design does not specify or alter the authorization mechanism.

Compatibility, Upgrade, and Coexistence

Compatibility

This design is compatible with the following component versions:

  • Java 1.5
  • OSGi R4
  • Eclipse 3.2
  • SOAP 1.1
  • Servlet 2.4

Installation and Configuration

Installation

The plug-in should be deployed into an Eclipse OSGi container in a manner conformant with OSGi. No special handling required.

Configuration

The connector will eventually leverage the standard configuration mechanism for Aperi plug-ins. At this time, it is uncertain whether that will leverage the OSGi Preference Service, the OSGi Configuration Admin Service, or some other mechanism for managing configurations. Whichever technique is chosen, the configuration of the Connector is simply a collection of name-value pairs.

In the meanwhile, the properties will be loaded into memory in the following manner:

  • Firstly, the system properties will be queried
  • If not found in the system properties, a property file will be queried

Configuration Properties

Property Description
aperi.connector.port The port on which the server will listen; defaults to 9000
aperi.connector.ssl.enabled Set true if ssl should enabled on the specified port; defaults to false.
aperi.connector.ssl.keystore pathname to key store file; defaults to /configuration/security/keystore.<extension>
aperi.connector.ssl.password The encrypted password
aperi.connector.ssl.keypassword The encrypted password to the keystore
aperi.connector.ssl.clientAuth Set true for two-way authentication; defaults to true
aperi.connector.ssl.algorithm The algorithm for ssl encryption; defaults to <Unknown>

Intellectual Property

The Raw Socket and Soap Service support on the same incoming port differs from known intellectual property.