Server configuration - Pyosch/powertac-server GitHub Wiki
Server configuration needs to be done at two different levels: The server itself must be informed about the session (or sessions) it is expected to run, and the individual modules must be configured in various ways.
Session configuration
A server can be run from a shell command-line, or it may be run under control of a Tournament Scheduler, and in either case it can be asked to run bootstrap or sim sessions. In version 0.1.0, the server takes a single command-line argument specifying a file, which in turn contains a sequence of very simple session specifications. This is causing problems as we start to think through how the server will interact with the Tournament Scheduler. So here is a proposal for an enriched CLI that should support the needs of both developers and the Tournament Scheduler:
Bootstrap mode
server --boot bootstrap-data [options]
where
bootstrap-data
is the name (not a URL) of the xml file that will be written with the results of the bootstrap run,- options include:
--control controller-url
gives the url of the Tournament Scheduler api, from which the server can request a configuration and a log-prefix string.--config server-config
giving the URL of a properties file that overrides the standard server configuration. If this option is missing and the--control
option is given, the server configuration is retrieved fromcontroller-url/server-config
.--log-suffix suffix
gives the root name for the log files, and defaults to "boot"; two log files are produced:powertac-suffix.trace
andpowertac-suffix.state
. If this option is missing and--control
is given, the logfile prefix will be retrieved fromcontroller-url/log-suffix
.
Sim mode
server --sim [options]
where options include the --config
, --log-suffix
, and --control
options as in bootstrap mode, as well as
--boot-data bootstrap-data
gives the URL of the xml file from which a bootstrap record can be read. If this option is missing and the--control
option is given, then the URL for the bootstrap record will becontroller-url/bootstrap-data
. Note: the server will not start if one of these two sources does not produce a valid bootstrap dataset.--jms-url url
gives the URL of the jms message broker, which is typically, but not necessarily, instantiated inside the server. The default value is tcp://localhost:61616.--brokers broker,...
is a comma-separated list of broker usernames that are expected to log in to the simulation before it starts. If this option is missing and--control
is provided, then the broker list will be retrieved fromcontroller-url/broker-list
.--log-suffix
defaults to "sim" rather than "boot".
Module configuration
The Power TAC server is a collection of configurable modules. The goal of this discussion is to arrive at a clear, consistent set of principles for how, when, and from where various configuration information is introduced to the server.
Here are some goals for an ideal configuration model:
- Information about what properties and behaviors are configurable should be readily accessible and understandable by all users who might have an interest in creating a non-standard configuration. This includes users who wish to create configurations by editing text files, those who wish to edit configurations through a simple web interface on the server, and those who wish to configure tournament or experimental games to be run at a later time.
- Since the server is modular, configuration data sources should also be modular.
- Configurability should not introduce unnecessary duplication. Ideally, the presence of a setter method with appropriate annotation and documentation would be enough. Otherwise, there is always the problem of keeping configurable code, documentation (including descriptions on web pages) and the config files in sync.
- It should not be necessary to maintain a global list of types that need to be configured. Rather, configurable types (or appropriate proxies) should ask to be configured during initialization. This is a feature of the old Apache Avalon/Excalibur framework, and a primary reason why the Spring configuration scheme is not adequate.
- Reasonable default values should be provided and documented. Ideally these are simply hard-coded to avoid duplication and extra work.
- A reasonable set of types for configuration values should be available, including String, integer and floating numbers, lists, and perhaps maps.
- Constraints on configuration values should be automatically communicated and applied to the extent possible.
- It should be possible to write expressions for configuration values that include the values of other configuration values as variables.
- Configuration expressions should be able to create and configure new instances of domain types, such as Gencos.
- Some configuration information needs to be packaged up and sent to brokers at the beginning of a game. This should also be the responsibility of the configurable types, not some centralized service. This is especially true when runtime parameters are computed from configured values, for example if configuration specified min and max values for some interest rate or charge, but brokers need to know the computed value.
Design approach
There are several possible approaches to this problem.
- The Excalibur framework includes a "Configurable" interface; at a specific point in the lifecycle of a Configurable component, an inherited method is called with its portion of the overall configuration tree. The keys in the configuration at tied to "role names" in the Excalibur setup.
- A top-down approach, in which some sort of mapping (possibly hard-coded) is used to map configuration data to components and their properties or setter methods.
- A bottom-up approach, as used in Magnet and in PowerTAC up to now, in which components invoke methods on a central 'properties' service to extract configuration data.
- An annotation-based approach, in which configurable elements are identified by annotation, and generic code reads the config data and applies the configurations.
We have chosen an annotation-based scheme. The design includes four elements:
- A set of annotation types.
@ConfigurableValue
labels a setter method or field as a configuration point, and@ConfigurableInstance
labels a class as capable of creating multiple configurable instances. If the value is to be published to brokers, then thepublish
property of the annotation must be set totrue
. - A service called a
Configurator
that takes a configuration (specifically, acommons.configuration.Configuration
) and a configurable instance or class, and applies the configurations by processing annotations. - A set of configuration files on the classpath (in the src/main/resources directories, or in the jarfiles, in either properties format identified as
config/*.properties
, or xml format identified asconfig/properties.xml
). Note that Java is not able to reliably find resources using wildcard searches in jarfiles in the classpath root (in other words, filenames at the top level under src/main/resources). - An optional user-supplied configuration file (or URL) that overrides properties set from classpath resources.
- A service called
ServerPropertiesService
that reads the configuration files, merges them into a singleConfiguration
, and waits for entities to request configuration and publish their settings for brokers.
Given the annotations and the ability to apply them, much of the rest can be provided by Apache Commons Configuration and some glue code.
The configuration data is published to brokers in the form of a Properties instance. Here's an example:
{tariffmarket.tariffMarketService.revocationFee=-133.8693054289303,
tariffmarket.tariffMarketService.publicationFee=-330.73270535433323,
auctioneer.auctionService.sellerSurplusRatio=0.5,
auctioneer.auctionService.defaultClearingPrice=40.0,
auctioneer.auctionService.defaultMargin=0.2,
accounting.accountingService.bankInterest=0.061056334512389523,
distributionutility.distributionUtilityService.defaultSpotPrice=-50.0,
distributionutility.distributionUtilityService.balancingCost=-0.04914701992562123,
distributionutility.distributionUtilityService.distributionFee=-0.013251367801793017}
Discovering configurable properties
To fully take advantage of this scheme, it is necessary to somehow discover the full set of configurable properties in a set of modules. Otherwise, it remains necessary to somehow discover configurable properties by reading documentation. It is not enough to be able to save the combined configuration drawn from a set of configuration files, because this gives no guarantee of completeness or correctness.
Propagating model state from bootstrap to sim
Some models have significant state that affects their energy use behavior or storage capacity. Failure to propagate this state from a boot session to a sim session could result in significant discontinuities and make predictions unnecessarily difficult for brokers (see Issue #755). So it seems we also need a mechanism to treat bootstrap state as configuration.
Analysis of the situation (prior to a fix for #755):
- The boot record is written in
CompetitionSetupService.saveBootstrapData()
. - Currently, there are two clauses in the boot record. Tags are "config" and "bootstrap". Both are communicated to brokers. The config section includes a single clause, the Competition instance. The bootstrap section contains customer usage, market, and weather data.
- In sim mode, data from the bootstrap record is read in two parts. The Competition instance is read in
CompetitionSetupService.preGame(URL)
and used to configure the current Competition instance and to initialize the clock. The bootstrap data is handled inprocessBootDataset(URL)
. - Module initialization happens before the boot record is processed, by the call to
CSS.preGame()
at the top ofCSS.preGame(URL)
.
Alternative approaches:
Ideally modules would encapsulate the details of their state data, and would initialize themselves from the boot record during initialization. This would require that the module-initialization data be pulled from the boot record before initialization.
- Initializable modules are already subclasses of InitializationService, which is currently an interface. If it were an abstract class, it could (potentially) provide access to the boot data. However, this would require that all existing modules be changed, and would break if any of them are already subclasses of some other class.
- The initialize() method already includes a reference to the Competition instance. Perhaps access to bootstrap data could be passed in the Competition? However, that would clutter up a domain type that is shared with brokers.
- A new BootstrapData service could be used, with the implementation in server-main. It would be handed at least the bootstrap-state clause before module initialization. Modules would then have to recover their bootstrap-state data from this service during module initialization.
- Perhaps the existing annotation-based configuration scheme can be extended to support bootstrap state. It might be a simple as adding a configuration clause to the boot record. That clause would then be added to the system configuration data before initializing modules.
Implementing an annotation-based approach
This would be an extension of the existing annotation-based configuration scheme, in which bootstrap state is just another source of configuration input.
Saving bootstrap state:
- State data must be recorded in the non-hierarchical format used by the configuration service, which is a "properties" format.
- State data must be gathered at the end of a boot session in a manner similar to the existing
ServerPropertiesService.publishConfiguration()
method. ThepublishConfiguration()
method is called by individual modules at the end of their respective initialize() methods. The cleanest way to gather state data would be to add an interface, perhapsBootstrapState
, with a methodgetBootstrapState()
.CSS.saveBootstrapData()
would then call these methods within a "bootstrap-state" clause. - The
ConfigurableValue
annotation would need an additional feature calledbootstrapState
that, if true, would cause the value to be gathered up as part of the bootstrap state. common.config.Configurator.gatherPublishedConfiguration()
is used to gather config data for publication to brokers. It, or something very much like it, could presumably be used to gather state. However, it does not currently handle configured instances, which is how the cold-storage model is set up. It explicitly stops at the class name. We could use theConfigurableInstance
annotation, assuming it's inherited. That can be fixed by annotating theConfigurableInstance
annotation itself with the @Inherited meta-annotation.- Note that in the case of configured instances, it does not make sense for the instances themselves to implement the
BootstrapState
interface, because they are not Spring services and so Spring will not find them. Instead, whatever Spring entity creates the instances should also be responsible for implementing the interface and saving their state.
Restoring bootstrap state:
- Data is in the bootstrap dataset in the form of an xml-serialized Properties instance.
- Modules are initialized in
CCS.runOnce()
. The bootstrap state needs to be added to the config before this point, preferably in CSS since that's where it's gathered and that's where the configuration is built. CCS.startSimSession()
callspreGame(bootDataset)
before callingCCS.runOnce()
.- Configured instances must not initialize state variables, because this will happen after the instance is configured. At best, a state variable can have a default value of null, and be initialized just in case the value is still null in the
initialize()
method.
Original (non-) design
As of version 0.1.0 (December 2011), the server is configured by reading several configuration files:
- The Spring config file
powertac.xml
, found in the classpath. In the development env, this is in server-main/src/main/resources. This file sets up Apache MQ, and injects configuration data into a number of specific class instances. For example, this is how thetimeslotPhase
value is set in the various service modules, thereby implementing the basic per-timeslot server activity cycle. This data clearly needs to be global to the server, rather than local to the individual services. - A properties file
server.properties
, also on the classpath. This is a properties-format file that sets basic competition parameters such as timeslot length, bootstrap length, etc. - A configuration file for household-customer.
- A configuration file for factored-customer.
- The bootstrap-data file.
- PluginConfig instances.
Clearly, neither the Spring context configuration nor simple Java properties files satisfies all our needs. Apache commons configuration comes closer, but by itself does not solve the duplication problem.