Skip to content
yairogen edited this page Nov 10, 2014 · 5 revisions

About

This guideline is intended for anyone who wishes to use the Foundation Configuration library. The library covers the following basic features:

  1. Create a single configuration object that includes all the configuration of an appliacation.
  2. A Factory API and a Spring Bean to get the configuration object into the application

The above supports 2 modes:

  1. Central Configuration (DB) mode
  2. File system mode

This will be detailed below.

Scope

This wiki page is developer oriented.

The Foundation Configuration library should be used in ALL Foundation java components.

Contact

Yair Ogen

Maven Info

<group>com.cisco.oss.foundation</group>
<artifcatId>configuration-api</artifcatId>
<version>Latest Version</version>

<group>com.cisco.oss.foundation</group>
<artifcatId>configuration-lib</artifcatId>
<version>Latest Version</version>

API Reference

How it works

Central Configuration Mode

Foundation Configuration library acts as a client for a "Central Configuration Repository". You can verify you are in this mode if you see in the log file:

Central Configuration IS enabled

The enable and disable of CCP is done via GIS using environment variables. By default ccp is not enabled. If you want to enable/disable it you can add to your cfg file:

CCP_ENABLED=Y

When CCP is enabled it will look for its data base connection details under the following environment variables:

# hold a host:port pair
CCP_SERVER

These properties should be a part of the user profile by default, hence allowing a single source for every component on a given host. However, you can override any of the above in the cfg file as mentioned above.

In order to support CCP a component needs to define the parameter set A.K.A Namespace. See below for more details.

File System Mode

This mode should be used by developers and / or QC as it works with simple xml/property files. You can verify you are in this mode if you see in the log file:

Central Configuration is NOT enabled

This library delivers one single configuration object based on several sources of xml files. Legacy property file approach is still supported.

In order to support CCP a component needs to define the parameter set A.K.A Namespace. See below for more details.

The created object is an apache common configuration Object with all the needed API to work with the configuration parameters.

The FoundationConfiguration Library in this mode supports 3 levels of configuration:

  1. The "Factory configuration" which is delivered as part of the components - is a simple xml file, called configSchema.xml that needs to sit in the root of the component jar. In this file will reside all the default configuration properties defined by the R&D for this specific component. The library will scan all the jars in the classpath in order to find all these files. The configuration object will include all the values defined in all these files.
  2. The "Deployment configuration" - which is external to the running jars and enables a "site/host" flavor of configuration. The library will search for a property file called deploymentConfig.properties. This file can be created by the PD line when they integrate the components to the solution required by the customer. This is a single file that sits in the classpath or in the root of a jar in the classpath. All the property values in this file will overwrite the default values defined in stage 1.
  3. The "Customer configuration" - which is external to the running jars and allows an instance-specific configuration set. The library will search for a single file called "config.properties" residing under the classpath. The aim of this file is to allow to configure properties that needs to be configured on site (such as URLs, users, passwords...). The library will take the configuration object and overwrite the stage 2 result with the properties values defined in this file.

After these 3 stages, the commons configuration object will be the final object that will be injected into the components all over the server.

Multi-instance Support

In some cases we a re required to support multiple instances of then same component running on the same host using the same install path. To allow loading different configuration files for each instance a new support was added. The new mechanism makes use of the system property "app.instance.name". When this property is set any non empty value, the configuration library will attempt to load a configuration file called: "config.<app.instance.name>.properties". If such a file does not exist the library will load the regular config.properties as it used to.

How to use the FoundationConfiguration library

Any component that wants to be FoundationConfiguration compliant needs to

  1. Install the Foundation Configuration Library dependency under Maven
  2. Expose the default configuration to the library
  3. Get the created configuration object, using the Factory API or using Spring Dependency Injection.

Expose the default configuration

In order to make the default configuration available to the Foundation Configuration library, the developer needs to add an xml file including the default configuration values to the released jar.

  • The format of this file needs to be xml file.
  • This file needs to be called configSchema.xml (the correct place is under main/resources dir).
  • The file needs to sit on the root of the delivered jar (handled automatically if, when using Maven, you put the file under src/main/resources).

Get the configuration object using the Factory API

import org.apache.commons.configuration.Configuration;
import com.cisco.oss.foundation.configuration.ConfigurationFactory;
...
Configuration configuration = ConfigurationFactory.getConfiguration();
...

Get the configuration object using Spring Dependency Injection

Here is the change you need to do to your Spring XML in order to get the Foundation Configuration object and to be able to inject it in your own bean.

...
<!-- this imports the Foundation configuration to your xml, including the single configuration object wrapped in a bean named "configuration"-->
<import resource="classpath:/META-INF/FoundationConfigurationInfraContext.xml" />
...
<bean id="sampleBean" class="com.cisco...SampleBeans" >
     <!-- inject the infra configuration bean to your applicative bean -->
     <property name="configuration" ref="configuration" />
</bean>
...

Property Place Holders

Foundation Configuration library enables you to have place holders in your spring xml files. For example look at this xml snippet:

<property name="myName" value="This is my name" />

It doesn't make any sense hard coding such a value. Spring enables you to use this form:

<property name="myName" value="${myNameConfigKey}" />

And now in a configuration source you can set:

myNameConfigKey = This is my name

In the xml it would look like:

<Parameter name="myNameConfigKey" type="STRING" description="my name">
    <DefaultValue>
        <PrimitiveValue value="This is my name"/>
    </DefaultValue>
</Parameter>

The Foundation Configuration enables this Spring feature and applies it to any of the configuration layers described above. The library will make sure that the config key is replaces by the Spring runtime before the spring bean and property are being processed.

How to enable dynamic reloading

CCP support dynamic reloading if the "requiresRestart" parameter is set to false in the Parameter xml root. When set to true the CCP will reload the new value in a pre-configured amount of time.

Reload Notification

Interested parties that wish to get notified on reload event, should use the following API:

FoundationConfigurationListenerRegistry.addFoundationConfigurationListener(configListener)

"configListener" above should be a class that implements the "FoundationConfigurationListener" interface.

Configuration

The following parameters enable this support even when CCP is not enabled:

  • configuration.dynamicConfigReload.enabled - this is the global parameter to enable the dynamic configuration. DEFAULT IS SET TO TRUE.
  • configuration.dynamicConfigReload.refreshDelay- set the refresh delay (milliseconds) to poll the file for changes. DEFAULT IS 30000 millis.

CCP Compliance

CCP Standards

Please follow these guidelines to be fully CCP compliant. Note: although in Runtime everything will work regardless of these conventions, it is very important to follow this so we have a consistent configuration rule-set across components.

File Structure

Users of the Foundation Configuration 2.x version are familiar with the layered approach of an optional defaultConfiguration.properties file that resides in the jar (/src/main/resources - in your development env.), and another mandatory config.properties files that resides in /etc (/src/test/resources in our development env.).

The problem with the property file approach is that we cannot express metadata on the parameters. For example, we cannot express is this parameter mandatory or not, or what is it's type etc.

From a developer perspective the changes checklist are as follows:

  • Convert the property file to an xml file - you do this by running the "proeprty_to_xml" script that comes with FoundationConfiguration tar file. Note the new file is named configSchema.xml and the old file should be renamed to defaultConfig.properties.old.

  • According to the configuration review performed earlier as described above edit the xml and add all relevant metadata.

  • Make sure every parameter defined in your config.properties exists in configSchema.xml. If you have a parameter that should not have a default value, just define the metadata and make sure it is marked as required. The system engineers will be prompted to set a value for this parameter when needed.

  • Minimum required attributes per Parameter are: "name" (provided by the conversion script), "description" and "type".

.

Configuration Naming Changes

CCP tries to create new standards for parameter names. Refer to this page for more info.

CCP XML Schema

The full ccp xsd file can be found [../../ccp_schema/CCP_XML.xsd here].

Main Parameter Attributes

Following are the main Parameter attributes and their meaning:

Attribute Default Description
requiresRestart true Means that the component needs to be restarted for it to consume the new parameter value, as opposed to being able to consume the new value without a restart
advanced false A parameter is advanced if it is to be changed by an expert engineer, usually in unusual/exceptional circumstances
readOnly false Immutable configuration parameter. Highly unusual.
required true A parameter is ‘required’ if the component needs that parameter to be assigned a value for it to work. There could be situations where a component does not require a parameter to have a value.
hidden false A hidden parameter will not be exposed by the CCP UI at all.
description N/A You must provide a meaningful (up to 512 characters) description of any parameter.
#### Requires Support

By default each parameter is required. If you want to mark a Parameter as optional please add the 'required="false"' attribute in the Parameter Element. E.g.

<Parameter name="optionalParam" type="STRING" description="TBD" required="false">
</Parameter>

Structures Tutorial

In some cases (especially in arrays) you would like to express that the Parameter has a structure. For example:

[Old Property Way]

rmiclient.1.host=localhost
rmiclient.1.port=1999
rmiclient.2.host=localhost2
rmiclient.2.port=2004

[New Xml Metadata]

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<NamespaceDefinitions xsi:noNamespaceSchemaLocation="../../ccp_schema/CCP_XML.xsd"
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <NamespaceDefinition>
        <NamespaceIdentifier version="TBD" name="TBD"/>
        <Parameter name="rmiclient" type="STRUCTURE" description="rmi client" isArray="true">
            <StructureDefinition>
                <StructureMemberDefinition name="host" type="STRING"></StructureMemberDefinition>
                <StructureMemberDefinition name="port" type="INTEGER"/>
            </StructureDefinition>
            <DefaultValue>
                <StructureValue index="1">
                    <StructureMemberValue name="host" value="localhost"/>
                    <StructureMemberValue name="port" value="1044"/>
                </StructureValue>
                <StructureValue index="2">
                    <StructureMemberValue name="host" value="localhost2"/>
                    <StructureMemberValue name="port" value="1045"/>
                </StructureValue>
            </DefaultValue>
        </Parameter>
    </NamespaceDefinition>
</NamespaceDefinitions>

Arrays Tutorial

Put aside structure arrays defined above you might have a regulat primitive array. Following is an example of a definition:

[Old Property Way]

myprop.nested.name.1=value
myprop.nested.name.2=value

[New Xml Metadata]

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<NamespaceDefinitions xsi:noNamespaceSchemaLocation="../../ccp_schema/CCP_XML.xsd"
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <NamespaceDefinition>
        <NamespaceIdentifier version="TBD" name="TBD"/>
        <Parameter name="myprop.nested.name" type="STRING" description="my custom primitive array" isArray="true">          
            <DefaultValue>
                <PrimitiveValue value="value1" index="1"/>
                <PrimitiveValue value="value2" index="2"/>
            </DefaultValue>
        </Parameter>
    </NamespaceDefinition>
</NamespaceDefinitions>

Relationships between dependencies (Controller)

In the excel template described above - we introduced the concept of controller. It is quite often that one or more parameters are dependant on a value of a another parameter. Usually if the other parameter has a certain value (or range) then these parameters are relevant. Following is an example of such a relationship (here we see that "service.rmi.retryOnTimeout" is only relevant if "service.rmi.numberOfRetries" is greater than 0):


        <Parameter name="service.rmi.retryOnTimeout" type="BOOLEAN"
                   description="if set to true, will cause the client infrastructure to re-send the current request to the server."
                   instantiationLevel="GLOBAL">
            <DefaultValue>
                <PrimitiveValue value="false"/>
            </DefaultValue>
            <EnabledBy parameterName="service.rmi.numberOfRetries" operator="GT">
                <Value>
                    <PrimitiveValue value="0"/>
                </Value>
            </EnabledBy>
        </Parameter>
        <Parameter name="service.rmi.numberOfRetries" type="INTEGER"
                   description="the number of times the client will retry connectiong to a server when facing communication errors"
                   instantiationLevel="GLOBAL">
            <DefaultValue>
                <PrimitiveValue value="3"/>
            </DefaultValue>
        </Parameter>

Structure Templates

In many cases you can find a need to define a template structure. This means you want to express the structure once and reuse many times.

The way to this is by using "ParameterType" instead of simple "Parameter". The ParameterType is similar to a java class (written once) which you can later on instantiate many times.

Example of a ParameterType:

<ParameterType name="service.rmi.base" type="STRUCTURE" description="The defaulat RMI Client Structure">
            <StructureDefinition>
                <StructureMemberDefinition name="innerPort" type="INTEGER" advanced="true" description="RMI is not firewall friendly. For this reason we support a port range in which the servers open a PTP connection with the client. The port range starts with this internal port value." />
                <StructureMemberDefinition name="numberOfRetries" type="INTEGER" advanced="true" description="the number of times the client will retry connectiong to a server when facing communication errors">
                    <DefaultValue>
                        <PrimitiveValue value="3" />
                    </DefaultValue>
                </StructureMemberDefinition>
                <StructureMemberDefinition name="retryDelay" type="INTEGER" advanced="true" unit="MILLISECONDS" description="the period of time to wait between retries that occur when communication errors are encoutered.">
                    <DefaultValue>
                        <PrimitiveValue value="1500" />
                    </DefaultValue>
                    <!-- <EnabledBy parameterName="service.rmi.numberOfRetries" operator="GT"> <Value> <PrimitiveValue value="0" /> </Value> </EnabledBy> -->
                </StructureMemberDefinition>
                <StructureMemberDefinition name="waitingTime" type="INTEGER" advanced="true" unit="MILLISECONDS"
                    description="if the number of retries was exausted the server instance that is not working is marked as down in internal client state. The client in return will not attemp to reconnect to the server for thie configured amount of time">
                    <DefaultValue>
                        <PrimitiveValue value="60000" />
                    </DefaultValue>
                </StructureMemberDefinition>
                <StructureMemberDefinition name="readTimeout" type="INTEGER" advanced="true" unit="MILLISECONDS" description="the default read timeout for the server. If in this perios of time, the client will not get any response an error will be thrown.">
                    <DefaultValue>
                        <PrimitiveValue value="300000" />
                    </DefaultValue>
                </StructureMemberDefinition>
                <StructureMemberDefinition name="writeTimeout" type="INTEGER" advanced="true" unit="MILLISECONDS" description="the default write timeout for the server. If in this perios of time, the client will not get any response an error will be thrown.">
                    <DefaultValue>
                        <PrimitiveValue value="5000" />
                    </DefaultValue>
                </StructureMemberDefinition>
                <StructureMemberDefinition name="connectTimeout" type="INTEGER" advanced="true" unit="MILLISECONDS" description="the default connect timeout for the server. If in this perios of time, the client will not get any response an error will be thrown.">
                    <DefaultValue>
                        <PrimitiveValue value="5000" />
                    </DefaultValue>
                </StructureMemberDefinition>
                <StructureMemberDefinition name="strategy" type="STRING" advanced="true" description="the load balancing strategy. can be either failOverStrategy or roundRobinStrategy">
                    <DefaultValue>
                        <PrimitiveValue value="failOverStrategy" />
                    </DefaultValue>
                    <Range>
                        <StringEnum value="failOverStrategy" />
                        <StringEnum value="roundRobinStrategy" />
                        <StringEnum value="activeActiveStrategy" />
                    </Range>
                </StructureMemberDefinition>

                <StructureMemberDefinition name="server" type="STRUCTURE" isArray="true" ignoreName="true">
                    <StructureDefinition>
                        <StructureMemberDefinition name="host" type="STRING" />
                        <StructureMemberDefinition name="port" type="INTEGER" />
                    </StructureDefinition>
                </StructureMemberDefinition>

            </StructureDefinition>                      

        </ParameterType>

Usage example (note the new "base" attribute):

        <Parameter name="service.rmi" type="STRUCTURE" description="default rmi structure instance" base="service.rmi.base">            
        </Parameter>

Grouping library Namespaces in parent rpm

In many cases you have a component that has internal libraries all owned by the same logical component. An example would be PPS. You have an rpm called pps which contains internal libs like: pps-db, pps-engine, pps-xml etc. Some of the libraies may contain configSchema.xml files of their own.

By default running the ccp maven plugin "as is" will cause multiple namespaces to appear in the ccpConfig.xml generated file. This is redundant. What we would like to happen is that all internal configurations are grouped with the rpm in a single namespace.

To achieve the above you need to add the "groupWith" attribute in the configSchema.xml file of each internal library (no need to change the configSchema in the rpm project if one exists). The value of the attribute will point to the rpm-software name as defined in the pom file of the rpm maven project.

Example in internal lib (e.g. pps-db):

<NamespaceDefinition>
        <NamespaceIdentifier version="TBD" name="TBD" groupWith="pps"/>

The above will actually generate a single namespace for pps instead of having multiple names (that mean nothing to operators).

CCP Maven Plugin

Components that produce rpm or tar files (e.g. represent processes) should add in their pom file the following maven plugin. This plugin will scan all configSchema.xml files in all the component Maven dependencies and build an xml that holds all the needed data for introducing a new Component into the CCP DB using the CCP GUI.

**The plugin also reads the relevant (configured) config.properties file

  • so it introduces parameter overrides "out of the box".**

The section to be added in the pom file (under build-->plugins) is (note the needed update to the version field):

    <plugin>
                <groupId>com.cisco.oss.foundation</groupId>
                <artifactId>ccp-maven-plugin</artifactId>
                <version>{LATEST_VERSION}</version>
                <configuration>
                    <configLocation>/src/main/cfg/config.properties</configLocation>
                </configuration>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>findConfigInDependencies</goal>
                        </goals>
                    </execution>
                </executions>
        </plugin>

Web UI Modules

Web UI Modules that run under a web container need special treatment. As the GIS script is generic for all the modules we need to read the generated xml file during runtime. For this each module as part of the mapping must copy the file to the "classes" folder ' in addition to the docs/ccp folder.

CCP Validation

The plugin will validate the generated file via a special python script.

Currently this validation will only run when the plugin is executed on a Linux host.

After the build has finished, look in the console of your job and search for "CCP Validation Results" and inspect if you have any warning or errors that need addressing.

Configuration Options
  • To disable this check add in your configuration section: 'false'.
  • If your component does not use database configuration, please add the following to your configuration section: 'false'.
  • If your component is not an RMI server, please add the following to your configuration section: 'false'.
  • If your component is not using Foundation monitoring, please add the following to your configuration section: 'false'.

Pom snippet example:

            <plugin>
                <groupId>com.cisco.oss.foundation</groupId>
                <artifactId>ccp-maven-plugin</artifactId>
                <version>{latest_version}</version>
                <configuration>
                     <configLocation>/src/main/cfg/config.properties</configLocation>
                     <!-- OPTIONALLY set a number of source locations for overrides -->
                     <!-- 
                          <configLocations>
                               <param>/src/main/cfg/config.properties</param>
                               <param>/src/main/cfg/deploymentConfig.properties</param>
                          </configLocations>
                     -->
                     <runCcpValidation>true</runCcpValidation>
                     <isDataBaseDefined>true</isDataBaseDefined>
                     <isRmiServerDefined>true</isRmiServerDefined>                  
                     <isMonitoringUsed>true</isMonitoringUsed>
                </configuration>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>findConfigInDependencies</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

See here for more info on CCP Validator.

Troubleshooting

Disabling Apache commons configuration character delimiter

Use the system property: "commonsConfig.delimiterParsingDisabled".

-DcommonsConfig.delimiterParsingDisabled=true

My tests fail even when I update the properties

It is common practice that tests use api like the following to update the configuration set (in-memory) for different tests.

config.setProperty(key,value);

The above seems to start working? The reason is probably the additional cache layer we added in front of the configuration object. The reason the cache was added is because we've found out that although commons-configuration library doesn't do I/O for each property read, it does do many locks. This is harmful in multi-threaded environment.

To over come this issue, in tests code only you can call this code AFTER you update the properties:

((FoundationCompositeConfiguration)config).clearCache();

Change Apache commons configuration character delimiter

Use the system property: "commonsConfig.defaultListDelimiter". Note: default is ','.

-DcommonsConfig.defaultListDelimiter=~

The above '~' is just an example. You can set any non-white-space character.

How to use configuration in testing

Goal

  1. Manage multiple configuration files for different test classes.
  2. Leave the config.properties file to be free of any test configuration parameters and allow it to be clean with values ready for integrator or system engineer (i.e. empty values for db username, password, etc.).

Usage

The following API's mandate that the file name used must contain the word "test" (regardless of the letter case) anywhere in the file name.

Maven Dependency

Please use the following maven dependency to get it:

<dependency>
   <groupId>com.cisco.oss.foundation</groupId>
   <artifactId>FoundationConfigurationForTest</artifactId>
   <version>{any released version}</version>
   <scope>test</scope>
</dependency>

Using API

Call the method

ConfigurationForTest.setTestConfigFile("myTestConfig.properties");

From you test code. For example:

@BeforeClass
public static void init() throws Exception {
   ConfigurationForTest.setTestConfigFile("myTestConfig.properties");
}