Version 3

    This document is currently a draft, and is subject to considerable modification.

     

    This document is meant to describe the design of the management API of an AS 7 domain and of a single AS 7 server instance running in standalone mode.

     

    The API is described as "detyped" to reflect the fact that the API only exposes a small number of types besides JDK classes.

     

    The goals are to:

     

    • Define a Java-based API that can be consumed by a remote java client that requires only a small number of types besides JDK classes to be on the client classpath. Any complex types involved in the API are expressed in terms of a fixed set of general purpose classes. The types needed on the client classpath are:
      • A library containing the small set of general purpose types (see discussion of MetaValue, MetaType below), plus some helper utilities (e.g. Builder classes) to assist in building up objects of those types.
      • 4 interface classes, each of which describes the methods exposed by one of the server side "controllers". The parameters and return types in these interfaces should be expressed in terms of the general purpose types. The methods in these interfaces will be limited and general purpose in nature, i.e. there will not be a myriad of detailed methods like "setHttpPort(int port)".  The 4 types of "controllers" are:
        • DomainController. Primary management point for an AS 7 domain. Responsible for all configurations in the domain.xml file. The DomainController is also intended to function as the central management point for all hosts and servers in the domain, delegating to internal APIs exposed by the HostControllers or ServerControllers as necessary. However, a running AS 7 domain does not require a running DomainController, so the HostControllers and ServerControllers will also expose a management API.
        • HostController. Responsible for controlling one or more AS 7 server instances on a physical or virtual host. Responsible for all configurations in the host.xml file for that host. Will also expose some host-specific management operations, e.g. start/stop/restart a server.
        • ServerController (domain mode): Provides a read-only view of an individual server's configuration. Provides access to any metrics exposed by the server. Provides access to any non-configuration-related management operations exposed by the server (e.g. remove message X from JMS queue Y.) Does not expose any ability to update the server's persistent configuration, as all aspects of a server's persistent configuration are the responsibility of either the DomainController or the host's HostController.
        • ServerController (standalone mode): Sole management point for a standalone AS 7 server instance.
      • Internal implementation classes related to the protocol for communicating with the "controllers". These are not part of the API but need to be on the client classpath.
    • On the server side, provide a straightforward mapping of the core detyped Java API to JMX. The intent is that most (hopefully all) attributes and operations exposed via the core API will be available via JMX.
    • On the server side, provide a straightforward mapping of the core detyped Java API to REST. The intent is that most (hopefully all) attributes and operations exposed via the core API will be available via REST.
    • Handle different types of management actions:
      • Reads of persistent configuration attributes
      • Writes of persistent configuration attributes
      • Reads of server level runtime state (i.e. metrics/statistics)
      • Updates to server level runtime state that do not impact persistent configuration
      • Discovery of what the manageable resources exposed by the system are, and what attributes and operations are supported by those resources.
    • Provide clean handling for operations that inherently impact multiple servers (i.e. updates to the domain.xml configuration model and any host.xml configuration.) Allow the end user to specify how those changes are applied to various server groups and the servers within those groups in a "management operation plan". After the operation is executed, provide detailed information on how the operation was executed on the various controllers and servers involved.
      • For management operation plans involving updates to persistent configuration, the plan will include information on what should happen in case the update cannot be applied successfully to one or more servers; i.e. whether the update should be applied to other servers where it has not yet been attempted or rolled back on servers where it has successfully been applied.
      • Hide some of the API complexity involved in the above by having a default update plan that concurrently applies the operation to all relevant server groups and all servers within the domain.
    • Management operation plans should include support for multiple operations that update persistent configuration that are to be performed as an atomic unit.

      • TODO: is it necessary to support plans with multiple operations that are not atomic; i.e. where failure of one operation will not cause the others to be rolled back? We currently do, but this adds conceptual complexity and clutters the API a bit.
    • Provide the ability to target operations that are not inherently multi-server in nature against a particular server. Such operations are:

      • Reads of persistent configuration attributes
      • Reads of server level runtime state (i.e. metrics/statistics)
      • Updates to server level runtime state that do not impact persistent configuration

     

    Other goals include:

    • Support for notifications. The detyped Java management API should include a general mechanism by which managed resources can advertise the availability of notifications. The detyped Java API should include a generic mechanism by which clients register a listener for notifications. This implies a persistent connection between the client and the server.
    • Ability to get progress events as multi-server operations are executed. Some operations may take a considerable amount of time to execute across the domain (e.g. a large, complex deployment applied to many servers); allowing callers to register a listener for progress events would be nice. The AS 7 Alpha1 domain deployment plan handling had a prototype of that kind of capability.

    MetaValues

     

    Method parameters and return types in the detyped API are expressed in terms of a limited number of general purpose classes known as "MetaValues". The types of MetaValues are:

     

    • SimpleValue -- a simple wrapper around one of a fixed list of JDK types (String, primitive wrapper types, Date, BigDecimal, BigInteger.
    • ArrayValue -- an array of other meta values, all of which are of the same type.
    • CollectionValue -- a collection of other meta values, all of which are of the same type.
    • MapValue -- a Map<MetaValue,MetaValue>.
    • CompositeValue -- a sort of Map<String, MetaValue> where the type of the MetaValue associated with a given key is fixed, but different keys can have values of different types. This type is used to model complex Java types, where each key/value pair in the map is analogous to a field in an ordinary Java type.
    • TableMetaValue -- a table where each row is a CompositeValue with an identical mapping of allowed keys and value types. The table definition includes an ordered list of which which keys are "index fields". A particular CompositeValue can be retrieved from the table by providing a list of MetaValues, each of which corresponds to one field in the index.

     

    In practice, the most commonly used types are SimpleValue, CollectionValue and CompositeValue.

     

    MetaTypes

     

    A MetaValue includes a read-only property of type "MetaType". Accessing a MetaValue's MetaType allows the caller to determine information about the value's type, in an analogue to how using reflection can determine information about an ordinary java object. A MetaType can also be used to validate a MetaValue; i.e. the MetaType can check that the value's composition is consistent with the MetaType's definition.

    An important problem to solve is making MetaTypes easy to use for end users. See below for more on this issue.

     

    Each of the value types described above has a corresponding MetaType.

     

    • SimpleValue exposes a SimpleMetaType. SimpleMetaType just exposes the class name of the wrapped value (java.lang.String, java.util.Date etc). There is a constant value (e.g. SimpleMetaType.STRING) for each of the legal types.
    • CollectionMetaType exposes the (single) MetaType of the MetaValues that comprise a CollectionValue.
    • ArrayMetaType exposes the (single) MetaType of the MetaValues that comprise an ArrayValue.
    • CompositeMetaType defines what the valid keys are in a CompositeValue, as well as what the MetaType must be for each key's value. It also includes a text description of each key. (Note: I think it should also expose whether a given key/value pair is optional.)
    • TableMetaType defines what the (single) CompositeMetaType is for the rows in a TableValue, along with what keys comprise the index.

     

    As mentioned above, an important problem to solve is making MetaTypes easy to use for end users. There are a number of requirements that need to be met:


    1. Every MetaValue emitted by the server must have a valid MetaType associated with it. Clients should be able to use the MetaType to introspect the value.
    2. Clients should not have to have a reference to a MetaType in order to construct a MetaValue. For example, say a particular operation exposed by a managed resource takes a parameter named "socket" whose type is a CompositeValue, and that value's MetaType defines a key named "name" that requires a String value, a key named "port" that requires an int value and an optional (nillable) key named "properties" that requires a Map<String, String>. A client should be able to construct a valid value simply by indicating that a CompositeValue is needed and by assigning appropriate values to "name" and "port". They should not need to provide the MetaType. There are a couple possible ways this might be handled:
      1. Don't require MetaValue to expose MetaType. Have a separate interface/class hierarchy, e.g. TypedMetaValue that exposes the MetaType. Servers always produce TypedMetaValue's, but clients can construct objects of a simpler MetaValue class hierarchy that don't expose (or require) the MetaType. This approach is conceptually complex though and breaks down in cases like TableValue, whose semantics require type information.
      2. Provide a sort of DSL/builder pattern to create MetaValues. The builder accumulates the information to create the MetaType at the same time it accumulates the information to compose the value. In the build() operation it actually constructs the MetaType and passes it to the constructor of the MetaValue. Emanuel Muckenhuber is exploring this approach.
    3. Clients (and the server) should be able to validate MetaValues they create against the expected MetaType. This validation should not overly rely on an exact match of MetaTypes (e.g. value.getMetaType().equals(anotherMetaType)). It should account for the possibility that the value's MetaType may not include optional information. For example, a client may create a CompositeValue to act as the "socket" parameter described above. The MetaType for that CompositeValue may not include information about the optional "properties" key. A validation of that value against the full formal definition of the parameter should not fail, since "properties" is optional. A missing "name" however should result in a validation failure.

     

    Managed Resources

     

    The API exposed by each controller (DomainController, HostController, domain-mode or standalone ServerController) is expressed in terms of attributes and operations exposed by a set of ManagedResources. A ManagedResource is conceptually similar to a JMX MBean, with the significant difference that ManagedResources are organized in a tree structure, whereas MBean need not have any inherent organization.

     

    ManagedResources expose a set of read-only attributes and a set of operations. Attributes expose the persistent configuration of the resource. Operations can be read-only (e.g. a metric), write-only, or read-write. Unlike in JMX, attributes are not read-write. The value of some attributes may be mutable, but can only be mutated via an operation. (The operation to mutate attribute "foo" may be named "setFoo", but it's still an operation.) Attributes are not used for metrics; they are only used to represent the persistent configuration of the resource. Metrics are retrieved using operations. (The operation to retrieve a metric "threadCount" may be called "getThreadCount" and not take any parameters, but it is still an operation.)

     

    Each resource also exposes meta-information about itself that clients can use to discover what attributes and operations are exposed by the resource. This meta-information is encapsulated in a ManagedResourceInfo object. A ManagedResourceInfo object is conceptually very similar to a javax.management.MBeanInfo.

     

    ManagedResources are organized in a tree structure. Each ManagedResource (except root resources; see below) must expose an attribute whose value is of some SimpleMetaType that acts as the unique identifier for the resource compared to all similar resources under the resource's parent resource. (TODO: consider requiring this value to be of SimpleMetaType.STRING.) The name of this attribute is arbitrary. The name of the attribute must be exposed via the resource's ManagedResourceInfo object, via the getIdAttributeName() method.

     

    Each controller (DomainController, HostController, domain-mode or standalone ServerController) is responsible for managing a resource tree. Each resource tree is rooted in a root ManagedResource. A ManagedResource exposes an isRoot()method that must return true for the root resource and false for all others. For a root resource, the ManagedResourceInfo.getIdAttributeName() method must return null, as a root resource does not require a unique identifier.

     

    Besides attributes and operations, a ManagedResource offers a read-only view of the child resources organized under it in the tree. Child resources are organized by type, with all resources of a given type exposing ManagedResourceInfo objects that are equal to each other. For each child resource type, the parent resource maintains a simple string identifier that represents the collection of resources of that type. For example, a ManagedResource representing a JBoss Web web server subsystem might have child resources that represent JBoss Web connectors, and would use the string "connectors" as the identifier for that collection of resources.

     

    The ManagedResourceInfo for a resource includes metadata about what types are child resources are available, what the name of the identifier is the parent uses for the collection of each type of child, and the cardinality of the relationship (typically 0..1 or 0..*). The lower limit must be zero otherwise a parent resource cannot exist without children, yet child resources cannot exist without a parent. (TODO there may be ways around this if it proves to be a problem.)

     

    Adding and removing child resources is accomplished by invoking operations on the parent. (TODO: another possibility is the (Domain/Host/Server)Controller API expose special "add" methods.)

     

    Addressing a ManagedResource

     

    From the above, a fairly simple scheme for addressing any resource in the tree becomes apparent. An address for a resource is an array or List of simple string pairs. The first string in the pair is the identifier the parent uses for the collection of child resources of the child's type. The second string is the value of whatever attribute the child resource identifies as its "id attribute".

     

    So, for example, the address of the resource representing an HTTP connector in the domain model might be expressed as:

     

    {profiles/web},{subsystems/web},{connectors/http}

     

    It's easy to see how that cold easily be mapped to a REST resource URL:

     

    http://127.0.0.1/domain/profiles/web/subsystems/web/connectors/http

     

    It also maps readily to a JMX ObjectName, where each ManagedResource is exposed in JMX as an MBean:

     

    jboss.as.domain:profiles=web,subsystems=web,connectors=http


    A not so nice thing in the above is an English issue where the plural form of a property name is not the same as its singular form, and in some contexts the singular form is more natural, e.g. in a key/value pair

     

    jboss.as.domain:profile=web,subsystem=web,connector=http

     

    There are a couple possibilities here; one is to always use the singular form, on the theory that a singular form in a REST URL is less unnatural than a plural form in an ObjectName. Another possibility is to have the ManagedResourceInfo metadata that describes the parent/child relationship include information about both the singular and plural forms. Different addressing systems would use the more "natural" form; parents can resolve children using either form.

     

    Client-side View of a ManagedResource

     

    The  management API provides a read-only view of the current state of a  ManagedResource's attributes. This is via the ManagedResource class in the detyped  API library. The ManagedResource class is simply another type of  MetaValue. The ManagedResourceInfo object it exposes is another type of  MetaType.

     

    Reading Attributes

     

    Directly via *Controller API:

     

    EntityAddress address = EntityAddress.fromString("profile/web/subsystem/web/connnector/http");
    MetaValue m = domainController.getAttribute(address, "socket");
    String socketName = (String) ((SimpleValue) m).getValue(); // yuck! how about SimpleMetaType.STRING.getValue(m);
    

     

    (TODO: quite likely EntityAddress can be replaced with a simple String).

     

    Indirectly via ManagedResource:

     

    EntityAddress address = EntityAddress.fromString("profile/web/subsystem/web/connnector/http");

    ManagedResource connector = domainController.getManagedResource(address);

    MetaValue m = connector.getAttribute("socket");

    String socketName = (String) ((SimpleValue) m).getValue(); // yuck! how about SimpleMetaType.STRING.getValue(m);

     

    Invoking Operations

    The operations exposed by a resource are described in its ManagedResourceInfo, via the ManagedResourceOperationInfo objects returned by its getOperations() method. The main information provided by a ManagedResourceOperationInfo object is the name of the operation and a description of the metatype of the parameters the operation takes.

     

    Invoking an operation consists of creating MetaValues for the parameters, and passing them, along with the name of the operation and the address of the target resource, to the invoke method.

     

    EntityAddress address = EntityAddress.fromString("profile/web/subsystem/foobar");
    MetaValue foo = createFooMetaValue();
    MetaValue bar = createFooMetaValue();
    domainController.invokeOnDomain("setFooBar", address, foo, bar);

     

    (TODO: clarify why I said "invokeOnDomain" above vs a more general "invoke" -- basically we need to distinguish operations that affect the domain, vs those that effect a single host, vs those that effect a single server, as the structure of the result varies. The "invokeOnDomain" is a nod in that direction.)

     

    The return type of the invocation depends on whether the operation targets the domain, a host or a single server. All operations have a core return type of some MetaValue; the MetaType of that MetaValue varies from one operation to another and is described in the ManagedResourceOperationInfo object that describes the operation. However, for operations that impact more than a single server, the core return value from each server needs to be encapsulated in a larger data structure that describes the overall impact of the operation across the domain.

     

    For example, an operation that effects a domain level resource would be executed by the DomainController as follows:

     

    • Handle the operation on the DomainController itself. For example, update the DC's copy of the domain configuration.
    • Ask all registered HostControllers to handle the operation (e.g. update their copy of the domain configuration) and reply with the list of servers that would be affected by the operation.
    • Execute tasks asking the HostControllers to instruct individual servers to execute the operation and reply with any return value.

     

    As a result, the return value provided by the DC would need to include the following information:

     

    • Failure information describing any failure to apply the operation on the domain controller. (TBD: just a String or some exception type. My inclination is to use a String, which loses stack trace information, but avoids all issues with server exception types leaking to the client. Stack traces can be logged on the server side, where they are more relevant.)
    • Failure information describing any failure to roll back the operation on the domain controller. (The rollback would occur following a successful application on the DC, but a failure on a HostController or server.)
    • Map<String, failure information> describing any failures to handle the operation on a HostController.
    • Map<String, failure information> describing any failures to roll back the operation on a HostController. (The rollback would occur following a successful application on the HostController, but a failure on another HostController or a server.)
    • Results by server

      • Identity of the server
        • name
        • host name (to allow correlation of results by host)
        • server group name (to allow correlation of results by server group)
      • Failure information describing any failure to handle the operation on the server
      • Failure information describing any failure to roll back the operation on the server
      • The return value of the operation.

     

    A similar process applies to operations that affect a host's configuration. Return values would include:

     

    • Failure information describing any failure to apply the  operation on the HostController.
    • Failure information describing  any failure to roll back the operation on the HostController. (The  rollback would occur following a successful application on the HostController, but a  failure on a server.)
    • Results by server

      • Identity of the server
        • name
        • host name (to allow correlation of results by host)
        • server group name (to allow correlation of results by server group)
      • Failure information describing any failure to handle the operation on the server
      • Failure information describing any failure to roll back the operation on the server
      • The return value of the operation.

     

    An operation targetted at a single server would have a simpler return type:

       

      • Failure information describing any failure to handle the operation on the server
      • Failure information describing any failure to roll back the operation on the server
      • The return value of the operation.

       

      TODO: decide how to represent all of the above. The domain-client module currently has a class for this for domain level deployment operations that could be adapted for general use. It can all be represented in a complex detyped object as well (CompositeValues and CollectionValues nested in other CompositeValues). That's painful for the end user, but reduces the number of client-side classes. For now the assumption is they will become complex detyped objects.

       

      Management Operations Plans

      TODO

      Notifications

      TODO