Access Management 7.3.2

Upgrade nodes and change node configuration

Over time, it may become necessary to change the schema of the configuration for your node.

When this happens, the changes must be propagated to the AM configuration system. To ensure an update of the AM configuration, use either of the following methods, depending on the stage of development:

  • In the development stage, give your nodes the special version number 0.0.0. Any AM configuration created by nodes that have this special version number is wiped on each restart of AM.

    If you are using custom nodes with version 0.0.0 in trees, you must remove them from the trees before restarting AM and reinsert them after the restart. If you do not do this, the entire tree cannot be viewed in the UI after the restart.
  • After moving to production and switching to semantic versioning, you must write upgrade functions into the node to locate existing configuration and convert it to the new schema.

    For information on upgrading schema in production mode, refer to Upgrade simple node configuration schema changes and Upgrade complex node configuration schema changes.

Upgrade simple node configuration schema changes

This section explains how to upgrade nodes with simple schema changes. For example, changing an attribute to a compatible type.

When configuration schema changes are simple, call the PluginTools#upgradeAuthNode(Class) method in the upgrade method of your plugin, as follows:

@Override
public void upgrade(String fromVersion) throws PluginException {
  pluginTools.upgradeAuthNode(MyCustomNode.class);
}

Examples of simple schema changes include:

  • Changing an attribute type to one that is backwards-compatible with any existing values.

    For example changing an integer to a string type, or T to Set<T>.

  • Adding a new attribute that has a default value defined.

    For example:

    public class MyCustomNode implements Node {
      public interface Config {
        @Attribute(order = 1)
        String existingAttribute();
        @Attribute(order = 2)
        default Integer newAttribute() {
          return 5;
        }
      }
      // ...
    }

Upgrade complex node configuration schema changes

This section explains how to upgrade nodes that are changing the configuration schema such that existing values would clash with the new schema. For example, changing an attribute to an incompatible type.

When configuration schema changes are complex, use the API provided in the com.sun.identity.sm package. In this example, version 1.0.0 of a node has the following configuration schema:

public interface Config {
    @Attribute(order = 1)
    String name();
}

Version 2.0.0 of the node requires the user’s given name and family name separately, rather than simply a name string. The config for version 2.0.0 is as follows:

public interface Config {
  @Attribute(order = 1)
  String givenName();

  @Attribute(order = 2)
  String familyName();
}

To upgrade this example node configuration, find all existing instances of configuration created by the version 1.0.0 node, find the current values for the name attribute, and split it on the first space character to use in the two new attributes.

The following code shows how to upgrade the schema of this example node:

@Override
public void upgrade(String fromVersion) throws PluginException {
  try {
    SSOToken token = AccessController.doPrivileged(AdminTokenAction.getInstance());
    String serviceName = MyCustomNode.class.getSimpleName();
    ServiceConfigManager configManager = new ServiceConfigManager(serviceName, token);

    // Read all the values from all node in all the realms that will need replacing
    OrganizationConfigManager realmManager = new OrganizationConfigManager(token, "/");
    Set<String> realms = ImmutableSet.<String>builder()
      .add("/")
      .addAll(realmManager.getSubOrganizationNames("*", true))
      .build();
    Map<Pair<Realm, String>, String> oldValues = new HashMap<>();
    for (String realm : realms) {
      ServiceConfig container = configManager.getOrganizationConfig(realm, null);
      for (String nodeId : container.getSubConfigNames()) {
        ServiceConfig nodeConfig = container.getSubConfig(nodeId);
        String name = nodeConfig.getAttributes().get("name").iterator().next();
        oldValues.put(Pair.of(Realms.of(realm), nodeId), name);
      }
    }

    // Do the upgrade of the schema
    pluginTools.upgradeAuthNode(MyCustomNode.class);

    // Remove the old value and set the new values
    for (Map.Entry<Pair<Realm, String>, String> nameForUpdate : oldValues.entrySet()) {
      String realm = nameForUpdate.getKey().getFirst().asPath();
      String nodeId = nameForUpdate.getKey().getSecond();
      String name = nameForUpdate.getValue();
      int spaceIndex = name.indexOf(" ");

      ServiceConfig container = configManager.getOrganizationConfig(realm, null);
      ServiceConfig nodeConfig = container.getSubConfig(nodeId);
      nodeConfig.removeAttribute("name");
      nodeConfig.setAttributes(ImmutableMap.of(
        "givenName", singleton(name.substring(0, spaceIndex)),
        "familyName", singleton(name.substring(spaceIndex + 1))));
    }
  } catch (SSOException | SMSException | RealmLookupException e) {
      throw new PluginException("Could not upgrade", e);
  }
  super.upgrade(fromVersion);
}