MongoDB internally defines a database as a set of collections, with each collection consisting of a set of documents, each of which is essentially a JSON object.  Additional python code has been written to use MongoDB as a configuration database.  These classes and routines can be imported as:

from psalg.configdb.typed_json import *
import psalg.configdb.configdb as configdb

Typed JSON

JSON does not provide any type information for the values it contains.  For a configuration database, this can be problematic, as some numeric values might be limited to integers or even a small set of integers.  Therefore, additional information is added to the JSON configuration object to provide type information.  Any field name ending in ":RO" is considered to be read-only and will not be displayed by the graphical editor.  An additional key, ":types:" will be added to the top level dictionary.  This key maps to a dictionary with roughly the same structure as the JSON object, except that instead of containing values, the dictionary contains type information.  This information is either:

The ":types:" dictionary also has an ":enum:" entry defining all of the enumeration types.  The keys of the ":enum:" dictionary are the enumeration types, and the value is a dictionary mapping enumeration names to integer values.

It is also assumed that any lists in the JSON object contain objects of the same type, so the ":types:" dictionary does not contain a list at that level but the type information for every element of the list.

A few top-level keys have predefined types, reflecting their use in configuration objects.  These reserved keys are:

The cdict Class

In order to simplify the creation of typed JSON objects, the typed_json module defines the cdict class.  The constructor for this class takes an optional argument which is either an instance of another cdict or a dictionary representing a typed JSON object that is used to initialize the new cdict.  The main methods for this class are:

The typed_json module also has a few helper functions to deal with typed JSON dictionaries.

The configdb Class

Configuration management with MongoDB is handled by the configdb class.   In general, we add additional documents to the database, but do not change existing ones, so we can keep a complete history of configuration changes.  The basic organization is:

The constructor for the configdb class has the form configdb(server, h=None, create=False, root="NONE"), where:

The methods in the configdb class are:

Design

Mike Browne designed this based roughly on Matt's configdb design document: https://docs.google.com/document/d/12BlCMCWGy3X9Z9QEZn9AtkjVipYA0_x3ZiyfDYrRo-c/edit.  Some things were changed, but this was the starting point.  Chris Ford worked with Murali Shankar on the backend http database development.

Code

The server-side code (which receives http requests on the database server) is here:  https://github.com/slac-lcls/psdm_configdb.  The client-side code (which forms the http requests) is here:  https://github.com/slac-lcls/lcls2/tree/master/psdaq/psdaq/configdb.


Configuration history management

Two configdb commands can be used to manage configuration history:


Configuration History and Keys

DISCLAIMER: The following are just deductions. I (Valerio) have no direct insight on how configuration keys work.

Mike Browne writes:  I think Valerio's comments above are pretty spot on.  The integer is essentially a version/commit number.  It uniquely identifies everything at a particular moment in time... you change stuff, you get a new key/number.  We're saving a complete history, with the idea if it gets too much at some point, someone in the future will write a history-pruning tool. (smile)

Two Suggested New Tools

Merging Schema Changes

There is a working example of schema update in psdaq/psdaq/configdb/hsd_config_update.py

issue: we don't handle merging of schema changes to the config objects

ric's figurative proposal:
- script to read db and write json file
- modify ts_config_store.py to put the new schema in dbase
- third script applies settings from json file to the dbase ignoring the ones
  that are dissimilar
(get_config.py has some of the tools to do this)

cpo proposal:
- one script (configdb_modify.py) that reads the configdb json, modifies it (with python code)
  and writes it back (a plug or a minus: we no longer have a script that knows the official
  state of the schema: the database itself is the source of truth).
  - if we ever lost the database we no longer know the configdb schemas

Another proposal (from Ric):

item 1:

ts_config_V1:
config[group][0][rate]=10Hz
             [1][rate]=100Hz
             [2][rate]=100Hz

need a procedure to produce:

ts_config_V2:
config[group][0][rate]=10Hz
             [2][rate]=100Hz

or

ts_config_V2:
config[group][0][rate]=10Hz
             [1][rate]=100Hz
             [2][rate]=100Hz
             [4][rate]=100Hz

Rename/Remove Old Configdb Objects

ability remove and hide (in the control gui) detector configurations that aren't needed anymore
(hide: prepend an "_"?, rename timing_1 to _timing_1?)
rename = delete+create

Ric added: Maybe this should be thought of as archiving unneeded ConfigDb objects and we should therefore provide a way to unarchive them for when archiving is done erroneously or we change our mind.  Need to consider how to handle the case when, for example, timing_1 is archived to _timing_1 and then a new timing_1 is created.

Mike Browne implemented the following rename/delete functionality.  Murali is implementing a web interface on top of this:

c2 = cdb.configdb(mdb.server, "SXR", create=True, root=dbname)
c2.add_alias("FOO")                                       # 0
c2.transfer_config("AMO", "BEAM", "evr0", "FOO", "evr3")  # 1
with pytest.raises(Exception):
    c2.transfer_config("AMO", "BEAM", "evr0", "FOO", "evr3")
print("Configs:")
c2.print_configs()
cfg1 =  c.get_configuration("BEAM", "evr0")
cfg2 = c2.get_configuration("FOO", "evr3")
# These should be the same except for the detector names!
del cfg1['detName:RO']
del cfg2['detName:RO']
assert cfg1 == cfg2
c2.rename_device("evr3", "evr7", "FOO")
cfg3 = c2.get_configuration("FOO", "evr7")
# These should be the same except for the detector names!
del cfg3['detName:RO']
assert cfg2 == cfg3
with pytest.raises(ValueError):
    c2.remove_device("evr6", "FOO")
c2.remove_device("evr7", "FOO")
with pytest.raises(ValueError):
    cfg4 = c2.get_configuration("FOO", "evr7")

Rename/Remove Discussion

with Chris Ford, Murali, Matt, cpo on April 17, 2024

Murali highlighted some important questions with the new rename/remove features.  Here they are, with answers from the group.  I believe the conclusions were unanimous:

Removal is more straightforward: a new key will be created with the appropriate device removed, but history will be preserved in the old keys.

These answers will require changes to Mike Browne's code which Murali has kindly agreed to do.

CLI Overview

$ configdb -h
usage: configdb [-h] [--url URL] [--root ROOT] {cat,rm,cp,mv,history,rollback,ls} ...

configuration database CLI

positional arguments:
  {cat,rm,cp,mv,history,rollback,ls}
    cat                 print a configuration
    rm                  remove a configuration
    cp                  copy a configuration (EXAMPLE: configdb cp --create --write tmo/BEAM/timing_0 newhutch/BEAM/timing_0)
    mv                  rename a configuration (EXAMPLE: configdb mv --write tst/BEAM/timing_45 timing_46)
    history             get history of a configuration
    rollback            rollback configuration to a specific key
    ls                  list directory contents

optional arguments:
  -h, --help            show this help message and exit
  --url URL             configuration database connection
  --root ROOT           configuration database root (default: configDB)


Configdb Server Logs

The configdb server runs on a subset of psdm[01-04], where multiple nodes are used in a "load balancer" manner.  You have login to each of these nodes and look for logfiles in /u1/psdm/logs/configdb* to find the one that contains the transaction you are interested in.

Sample curl Commands

From Murali (for the dev configdb).

Get configuration for a device
curl -s -u "tstopr:passwordremoved" "https://pswww.slac.stanford.edu/ws-auth/devconfigdb/ws/configDB/get_configuration/tst/BEAM/trigger_0/"

Rename device
curl -s -u "tstopr:passwordremoved" "https://pswww.slac.stanford.edu/ws-auth/devconfigdb/ws/configDB/rename_device/tst/BEAM/trigger_0/?newname=trigger_1"

Remove device
curl -s -u "tstopr:passwordremoved" "https://pswww.slac.stanford.edu/ws-auth/devconfigdb/ws/configDB/remove_device/tst/BEAM/trigger_0/"