-
Notifications
You must be signed in to change notification settings - Fork 3
Home
This guideline is intended for anyone who wishes to use the Foundation Configuration library. The library covers the following basic features:
- Create a single configuration object that includes all the configuration of an appliacation.
- A Factory API and a Spring Bean to get the configuration object into the application
The above supports 2 modes:
- Central Configuration (DB) mode
- File system mode
This will be detailed below.
This wiki page is developer oriented.
The Foundation Configuration library should be used in ALL Foundation java components.
<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>
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.
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:
- 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.
- 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.
- 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.
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.
Any component that wants to be FoundationConfiguration compliant needs to
- Install the Foundation Configuration Library dependency under Maven
- Expose the default configuration to the library
- Get the created configuration object, using the Factory API or using Spring Dependency Injection.
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).
import org.apache.commons.configuration.Configuration;
import com.cisco.oss.foundation.configuration.ConfigurationFactory;
...
Configuration configuration = ConfigurationFactory.getConfiguration();
...
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>
...
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.
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.
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.
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.
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.
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".
.
-
Take special note for structures.
-
Components that produce rpm or tar files (e.g. represent processes) should add a special maven plugin for CCP.
CCP tries to create new standards for parameter names. Refer to this page for more info.
The full ccp xsd file can be found [../../ccp_schema/CCP_XML.xsd here].
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. |
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>
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>
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>
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>
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>
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).
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 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.
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.
- 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.
Use the system property: "commonsConfig.delimiterParsingDisabled".
-DcommonsConfig.delimiterParsingDisabled=true
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();
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.
- Manage multiple configuration files for different test classes.
- 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.).
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.
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>
Call the method
ConfigurationForTest.setTestConfigFile("myTestConfig.properties");
From you test code. For example:
@BeforeClass
public static void init() throws Exception {
ConfigurationForTest.setTestConfigFile("myTestConfig.properties");
}