The Carbon Java Framework  

The Carbon Deployment Module

Deployment Service Usage

Author: Doug Voet (dvoet at sapient.com)
Version:
Created:

Overview

This document describes what the Deployment Service is, how it works, and how to set it up.

Purpose of the Deployment Service

The Deployment Service is designed to help manage differences in configurations between application deployments. A deployment is defined as an instance of an application running within an environment. This service allows configurations for all environments and all instances within each environment to be stored separately then chooses the right configuration at runtime for each environment/instance. This allows all configurations to reside under source control and alleviates the error prone task of maintaining manually modified files on each deployment.

For example, a project may have a development team of 15 engineers, 2 test instances, 2 staging instances and 4 instances in production. Each instance within each environment must be configured with its host and port. Without this service, each of the 23 deployed instances would have a configuration file that would be manually modified to contain the correct data. Historically, this kind of process is error prone, difficult to manage, and a constant headache for project teams. This service allows all 23 configurations to reside in the source code repository and be managed as code.

Details

The Underpinnings

The Deployment Service relies on several pieces of functionality built into Carbon:

Configuration links
The Deployment Service works by setting up a link within configuration to the correct set of configurations for the current deployment. The effect of this is that, to the running system, the deployment-specific configurations always appear to be in the same place. This means that references to these configurations do not need to change from deployment to deployment. For more information on configuration links, see Configuration Service Links.
Configuration attribute replacement
The Configuration Service allows configuration attributes to be replaced by attributes in other configuration files or by deployment properties. This functionality is used within the Deployment Service's configuration to get the environment and instance names from the deployment properties and by custom configuration files to reference deployment-specific configuration values. For more information on configuration property replacement, see the Configuration Service documentation.
PropertyConfiguration inheritance
PropertyConfigurations are used to define a base configuration for an environment that can be augmented by information specific to each instance. This allows properties that only vary by environment to be defined in a single place for each environment. For example, 2 properties are needed for an application, QuoteService and Localhost. All instances of the application within an environment use the same QuoteService, but they each reside on difference machines, so Localhost differs from instance to instance. Using PropertyConfiguration inheritance, QuoteService can be defined in one place, while Localhost is defined for each instance. For more information on configuration links, see the Configuration Service documentation.

How It Works

The diagram below shows the basic setup for two scenarios. In the development environment, configurations don't vary based on the instance. In the production environment, they do. In the diagram, clients are configurations that require deployment specific values. They reference these values by using configuration attribute replacement, looking for an attribute contained with the CurrentDeploymentLink. The CurrentDeploymentLink is a LinkNode that is manipulated by the Deployment Service to point at the correct target. In the case of the development environment, the target is the development configuration. In the case of the production environment, the target is the instance configuration within the production environment. Note that the instances within production extend a production configuration. The production configuration would contain attributes that do not vary based on instance.

Configuration

The Deployment Service needs to know which environment and instance are currently running. This can be established by one of two ways. First, either the environment name and/or instance name can be defined directly in the configuration file. This means that the names can actually be stored as Deployment Properties. The second way is to create a NameLookup component that knows how to get the environment or instance name from some other location. In this case, the NameLookup component would be defined in the configuration file. Note that these two methods can work together. A NameLookup can be defined to specify the instance name and the environment name can be defined directly in configuration or vice versa. If the instance name is not defined, the service creates a link to a configuration named by the environment. If the instance is defined, the service creates a link to a configuration named by the instance within a folder named by the environment. See the following example of a Deployment Service configuraiton.

The following example defines environment and instance names directly within the configuration file. It also uses Deployment Properties to look up the actual values (thus the {...} notation).

Example Deployment Service Configuration

<Configuration
  ConfigurationInterface="org.sape.carbon.services.deployment.DeploymentServiceConfiguration">

    <FunctionalImplementationClass>org.sape.carbon.services.deployment.DefaultDeploymentServiceImpl</FunctionalImplementationClass>
    <FunctionalInterface>org.sape.carbon.services.deployment.DeploymentService</FunctionalInterface>

    <DeploymentsNodeAbsoluteName>/deployment/deployments</DeploymentsNodeAbsoluteName>
    <CurrentDeploymentNodeName>CurrentDeploymentLink</CurrentDeploymentNodeName>
    <EnvironmentName>{carbon.environment.name}</EnvironmentName>
    <InstanceName>{carbon.instance.name}</InstanceName>

</Configuration>

The next example uses a LocalHostNameLookup define the instance name. To create your own NameLookup, create a component that uses org.sape.carbon.services.deployment.namelookup.NameLookup as its functional interface.

Example Deployment Service Configuration

<Configuration
  ConfigurationInterface="org.sape.carbon.services.deployment.DeploymentServiceConfiguration">

    <FunctionalImplementationClass>org.sape.carbon.services.deployment.DefaultDeploymentServiceImpl</FunctionalImplementationClass>
    <FunctionalInterface>org.sape.carbon.services.deployment.DeploymentService</FunctionalInterface>

    <DeploymentsNodeAbsoluteName>/deployment/deployments</DeploymentsNodeAbsoluteName>
    <CurrentDeploymentNodeName>CurrentDeploymentLink</CurrentDeploymentNodeName>
    <EnvironmentName>{carbon.environment.name}</EnvironmentName>
    <InstanceNameLookup ConfigurationInterface="org.sape.carbon.core.component.ComponentConfiguration">
      <FunctionalImplementationClass>org.sape.carbon.services.deployment.namelookup.LocalHostNameLookup</FunctionalImplementationClass>
      <FunctionalInterface>org.sape.carbon.services.deployment.namelookup.NameLookup</FunctionalInterface>
    </InstanceNameLookup>

</Configuration>

DeploymentsNodeAbsoluteName specifies the location of the deployment specific configurations. CurrentDeploymentNodeName specifies the name of the link the service needs to create as well as the name of the link clients should reference. Below is the configuration folder structure for the first example.

Folder Structure

/deployment/deployments
    +--CurrentDeploymentLink
    +--Development
    +--Production
        +--Production
        +--Instance1
        +--Instance2

The items in bold above are the targets of CurrentDeploymentLink. Note the document named Production within the folder named Production. That document contains the configuration values that do not vary by instace. It is the configuration extended by Instance1 and Instance2. In order for Instance1 and Instance2 to extend Production, they all must be PropertyConfigurations.

Property replacement can now be used within other configurations to access the deployment specific configuration values. For example, a configuration of type QuoteServiceConfiguration has a property named QuoteServiceURL. The configuration would be as follows:

QuoteServiceConfiguration

<Configuration
  ConfigurationInterface="...QuoteServiceConfiguration">

    <QuoteServiceURL>{/deployment/deployments/CurrentDeploymentLink$QuoteServiceURL}</QuoteServiceURL>

</Configuration>

The actual value for QuoteServiceURL would then be retrieved at runtime from CurrentDeploymentLink's target, taking the value of QuoteServiceURL within that configuration.

Step By Step Setup

  1. Configure the DeploymentService: create a deployment directory off the root directory of your configuration service and place a DeploymentService.xml file in it. The xml file should be similar to the ones shown in the examples.
  2. Create the deployments directory: create a directory called deployments within the deployment directory.
  3. Create environment directories (required for environments that have different configurations for each instance): for each environment, create a directory within deployments matching the name of the environment.
  4. Create environment configurations: for each environment, create a configuration file within it's environments directory. These configurations should be PropertyConfigurations. All environment specific configuration values should be in these files.
  5. Create instance configurations: place a configuration file for each instance of an environment within the environment's directory. These configurations should be PropertyConfigurations and should extend the environment configuration.
  6. Add the deployment server as a startup component: add the following xml to the /core/StartupService configuration (see the Startup Service documentation for more information):
        <StartupComponent ConfigurationInterface="org.sape.carbon.core.component.startup.StartupServiceConfiguration$StartupComponentConfiguration">
            <ComponentName>/deployment/DeploymentService</ComponentName>
            <Enabled>true</Enabled>
        </StartupComponent>
    
  7. Supply deployment properties if applicable: the best way to do this is to change your application startup script to supply system properties to the application via -D commandline arguments to the JVM. In some cases, application servers do something similar (e.g. WebLogic 6 uses domain and server names); those properties could be used as well. An alternative to defining system properties is to put the deployment properties in the CarbonDeploymentConfig.properties file. This method should only be used if your configuration varies accoss instances and you need to run multiple instances of your application within a single JVM. This requires additional changes to your build process.

Note: If the environment does not have instance specific configuration, the environment configuration can be placed in the deployments directory instead of the directory for that environment like the development environment in the example. In this case, the value for InstanceName must be "" or the entry must be absent from the Deployment Service configuration.

Case Example: The Carbon Framework Team's Usage

Environment Description

Development

The framework team has the unique requirement of supporting multiple application servers (WebLogic and WebSphere). In general, engineers share a common database server, but each engineer has their own schema. Engineers using WebLogic share a set of configuration values for specifying the initial context factory and JNDI information and those using WebSphere share a different set. The team also is in the not-so-unique situation of being spread across multiple geographies (Cambridge, San Francisco and Delhi).

Continuous Integration Builds

The framework team runs two continuous integration builds, 1 for WebLogic on Solaris and 1 for WebSphere on Linux.

Environment and Instance Structure

Development deployment is separated into 2 environments, delhi-dev and camb-dev (San Francisco shares Cambridge's environment). Within each environment, each engineer has their own instance. To accommodate a shared database server, each environment has a base property configuration file that contains a URL to locate the database. To accommodate different WebLogic and WebSphere configurations, there is a property configuration file for each application server that extends the base configuration. Each engineer's instance configuration then extends either the WebLogic or WebSphere configuration to add database user specific properties.

The build has its own environment. Neither of the build instances share common configuration settings so there is no base properties configuration for the environment. Each instance has its own configuration file with a full set of properties.

Framework Deployments Directory Structure

build                <- directory for the build environment
    +--bilbo.xml     <- configuration for WebSphere build
    +--ganesh.xml    <- configuration for WebLogic build

camb-dev             <- directory for the cambridge development environment
    +--env.xml       <- base configuration cambridge dev

    +--weblogic.xml  <- weblogic configuration, extends env.xml
    +--websphere.xml <- websphere configuration, extends env.xml

    +--dvoet.xml     <- these are engineer specific configuration,
    +--ghinkl.xml    <- weblogic.xml or websphere.xml
    +--jreed.xml     <-/

delhi-dev            <- directory for the delhi development environment
    +--env.xml       <- base configuration delhi dev

    +--weblogic.xml  <- weblogic configuration, extends env.xml
    +--websphere.xml <- websphere configuration, extends env.xml

    +--araman.xml    <- these are engineer specific configuration,
    +--atayal.xml    <- weblogic.xml or websphere.xml
    +--sachop.xml    <--/
    +--vkirub.xml    <-/

Sample files from camb-dev environment:

/deployment/camb-dev/env Configuration

<Configuration ConfigurationInterface="org.sape.carbon.core.config.PropertyConfiguration">

    <!-- specifies the url of the database to use -->
    <DbUrl>jdbc:oracle:thin:@server:1521:sid</DbUrl>
    <SmtpHost>relaymail.xyz.com</SmtpHost>

</Configuration>

/deployment/camb-dev/weblogic Configuration

<Configuration ConfigurationInterface="org.sape.carbon.core.config.PropertyConfiguration"
    Extends="/deployment/camb-dev/env">

    <!-- initial context factory to be used for jndi look ups -->
    <InitialContextFactory>weblogic.jndi.WLInitialContextFactory</InitialContextFactory>

    <!-- jndi provider url -->
    <JndiProviderUrl>t3://localhost:7001</JndiProviderUrl>

    <JndiPrincipal>system</JndiPrincipal>

    <JndiCredentials>administrator</JndiCredentials>

</Configuration>

/deployment/camb-dev/dvoet Configuration

<Configuration ConfigurationInterface="org.sape.carbon.core.config.PropertyConfiguration"
    Extends="/deployment/camb-dev/weblogic">

    <!-- specifies the database user id -->
    <DbUserId>dvoet</DbUserId>

    <!-- specifies the database password -->
    <DbPassword>dvoet</DbPassword>

    <!-- Override the JNDI password -->
    <JndiCredentials>bigsecret</JndiCredentials>

</Configuration>

Referencing Deployment Specific Properties

The values specified in the deployment service configuration files are referenced from other configuration files in the EJB, Mail, and SQL service tests. Here is an example from the EJB service test:

/ejb/test/RemoteHomeFactoryTest Configuration

<Configuration ConfigurationInterface="org.sape.carbon.services.ejb.remote.RemoteHomeFactoryConfiguration">
    <FunctionalInterface>org.sape.carbon.services.ejb.remote.RemoteHomeFactory</FunctionalInterface>
    <FunctionalImplementationClass>org.sape.carbon.services.ejb.remote.RemoteHomeFactoryImpl</FunctionalImplementationClass>
    <Logger>ref:///log/LogService</Logger>

    <!-- Default values for lookup -->
    <InitialContextFactory>{/deployment/deployments/CurrentDeployment$InitialContextFactory}</InitialContextFactory>
    <ProviderUrl>{/deployment/deployments/CurrentDeployment$JndiProviderUrl}</ProviderUrl>
    <RmiIiop>true</RmiIiop>
    <Principal>{/deployment/deployments/CurrentDeployment$JndiPrincipal}</Principal>
    <Credentials>{/deployment/deployments/CurrentDeployment$JndiCredentials}</Credentials>

    <!-- Remote EJB used for testing -->
    <EnterpriseBean>
        <LogicalName>org.sape.carbon.services.ejb.remote.test.Tester</LogicalName>
        <HomeInterface>org.sape.carbon.services.ejb.remote.test.TesterHome</HomeInterface>
        <Cacheable>false</Cacheable>
    </EnterpriseBean>
    <EnterpriseBean>
        <LogicalName>org.sape.carbon.services.ejb.local.test.LocalGateway</LogicalName>
        <HomeInterface>org.sape.carbon.services.ejb.local.test.LocalGatewayHome</HomeInterface>
        <Cacheable>false</Cacheable>
    </EnterpriseBean>
</Configuration>


Copyright © 2001-2003, Sapient Corporation