qpid-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From conflue...@apache.org
Subject [CONF] Apache Qpid > QMFv2 API Proposal
Date Fri, 11 Dec 2009 20:47:00 GMT
<html>
<head>
    <base href="http://cwiki.apache.org/confluence">
            <link rel="stylesheet" href="/confluence/s/1519/1/1/_/styles/combined.css?spaceKey=qpid&amp;forWysiwyg=true" type="text/css">
    </head>
<body style="background-color: white" bgcolor="white">
<div id="pageContent">
<div id="notificationFormat">
<div class="wiki-content">
<div class="email">
     <h2><a href="http://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal">QMFv2 API Proposal</a></h2>
     <h4>Page <b>edited</b> by             <a href="http://cwiki.apache.org/confluence/display/~tross">Ted Ross</a>
    </h4>
     Imported text from Ken Giusti
          <div id="versionComment" class="noteMacro" style="display:none; padding: 5px;">
     Imported text from Ken Giusti<br />
     </div>
          <br/>
     <div class="notificationGreySide">
         <h1><a name="QMFv2APIProposal-Proposalfor%22NextGeneration%22QMFAPI"></a>Proposal for "Next Generation" QMF API</h1>


<h2><a name="QMFv2APIProposal-Goals"></a>Goals</h2>

<ul>
	<li>Simplify QMF API</li>
</ul>


<ul>
	<li>Use new QPID Messaging API</li>
</ul>


<ul>
	<li>Implement QMF protocol via QPID Map messages</li>
</ul>


<ul>
	<li>Improve thread safety by simplifying the callback notification mechanism.</li>
</ul>


<ul>
	<li>Reorganize QMF code into common API, Console API, and Agent API modules.</li>
</ul>




<h2><a name="QMFv2APIProposal-CommonObjectClasses"></a>Common Object Classes</h2>

<p>Object classes that are used by Agent and Console components.</p>

<h3><a name="QMFv2APIProposal-Schema"></a>Schema</h3>

<p>Schemas are used by QMF to describe the structure of management data.</p>

<h4><a name="QMFv2APIProposal-SchemaTypes"></a>Schema Types</h4>

<p>There are two classes (or types) of Schema - those that describe<br/>
QmfData objects, and those that describe QmfEvent objects.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>SchemaTypeData:
SchemaTypeEvent:
</pre>
</div></div>

<p>These types may be represented by the strings "data" and "event",<br/>
respectively. </p>

<h4><a name="QMFv2APIProposal-SchemaIdentifier"></a>Schema Identifier</h4>

<p>Schema are identified by a combination of their package name and class<br/>
name.  A hash value over the body of the schema provides a revision<br/>
identifier.  The class SchemaClassId represents this Schema<br/>
identifier.</p>


<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class SchemaClassId:
      &lt;constructor&gt;( package=&lt;package-name-str&gt;, 
                     class=&lt;class-name-str&gt;,
                     type=&lt;SchemaTypeData|SchemaTypeEvent&gt;)
                     hash=&lt;hash-str, format="%08x-%08x-%08x-%08x"&gt;, 
      .getPackageName(): returns &lt;package-name-str&gt;
      .getClassName(): returns &lt;class-name-str&gt;
      .getHashString(): returns &lt;hash-str, "%08x-%08x-%08x-%08x"&gt;
      .getType(): returns SchemaTypeObject or SchemaTypeEvent
      .mapEncode(): returns a map encoding of the instance.
</pre>
</div></div>

<p>If the hash is not supplied, then the default value<br/>
"00000000-00000000-00000000-00000000" will be supplied.  This may be<br/>
the case when a SchemaClass is being dynamically constructed, and a<br/>
proper hash is not yet available.</p>

<p>The map encoding of a SchemaClassId:</p>

<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>Index</th>
<th class='confluenceTh'>Optional</th>
<th class='confluenceTh'>Type</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"package_name"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>The name of the associated package.</td>
</tr>
<tr>
<td class='confluenceTd'>"class_name"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>The name of the class within the package.</td>
</tr>
<tr>
<td class='confluenceTd'>"type"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>The type of schema, either "data" or "event".</td>
</tr>
<tr>
<td class='confluenceTd'>"hash_str"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>The MD5 hash of the schema, in the format "%08x-%08x-%08x-%08x"</td>
</tr>
</tbody></table>

<h4><a name="QMFv2APIProposal-SchemaForDescribingThePropertiesofData"></a>Schema For Describing The Properties of Data</h4>

<p>The SchemaProperty class represents the description of a single<br/>
data item.  A SchemaProperty is a list of named attributes and their<br/>
values.  QMF defines a set of primative attributes.  An application<br/>
can extend this set of attributes with application-specific<br/>
attributes.  </p>

<p>All application-specific attributes must be named using the prefix "x-",<br/>
and their values must be encoded by an AMQP type.</p>

<p>Once instantiated, the SchemaProperty is immutable.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class SchemaProperty:
      &lt;constructor&gt;( name=&lt;name-value&gt;, 
                     type=&lt;type-value&gt;,
                     ...)
      .getType(): AMQP typecode for encoding/decoding the property data
      .getAccess(): "RC"=read/create, "RW"=read/write, "RO"=read only (default)
      .isOptional(): True if this property is optional
      .getUnit(): string describing units (optional)
      .getMin(): minimum value (optional)
      .getMax(): maximum value (optional)
      .getMaxLen(): maximum length for string values (optional)
      .getDesc(): optional string description of this Property
      .getDirection(): "I"=input, "O"=output, or "IO"=input/output
                       (required for method arguments, otherwise
                       optional)
      .getSubtype(): string indicating the formal application type 
                     for the data, example: "URL", "Telephone number",
                     etc.
      .isPolled(): True if changes to the data cannot be practically 
                   monitored by an Agent.  Such a data item can only
                   be queried or polled - not published on change.
      .getReference(): if type==objId, name (str) of referenced class
                       (optional) 
      .isParentRef(): True if this property references an object in
                       which this object is in a child-parent
                       relationship.
      .getAttribute("name"): get the value of the attribute named
          "name". This method can be used to retrieve
          application-specific attributes.  "name" should start with
          the prefix "x-"
      .mapEncode(): returns a map encoding of the instance.
</pre>
</div></div>

<p>The map encoding of a SchemaProperty's body:</p>

<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>Index</th>
<th class='confluenceTh'>Optional</th>
<th class='confluenceTh'>Type</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"qmf_type"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>integer</td>
<td class='confluenceTd'>The QMF type code indicating property's data type.</td>
</tr>
<tr>
<td class='confluenceTd'>"access"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>The access allowed to this property, default "RO"</td>
</tr>
<tr>
<td class='confluenceTd'>"optional"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>boolean</td>
<td class='confluenceTd'>True if this property is optional, default False</td>
</tr>
<tr>
<td class='confluenceTd'>"unit"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>Description of the units used to express this property.</td>
</tr>
<tr>
<td class='confluenceTd'>"min"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>integer</td>
<td class='confluenceTd'>The minimum allowed value for this property</td>
</tr>
<tr>
<td class='confluenceTd'>"max"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>integer</td>
<td class='confluenceTd'>The maximun allowed value for this property</td>
</tr>
<tr>
<td class='confluenceTd'>"maxlen"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>integer</td>
<td class='confluenceTd'>For string types, this is the maximum length in bytes required to represent the longest permittable instance of this string.</td>
</tr>
<tr>
<td class='confluenceTd'>"desc"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>Human-readable description of this property.</td>
</tr>
<tr>
<td class='confluenceTd'>"dir"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>Direction for an argument when passed to a Method call: "I", "O", "IO", default value: "I"</td>
</tr>
<tr>
<td class='confluenceTd'>"subtype"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>Type information for use by the application.</td>
</tr>
<tr>
<td class='confluenceTd'>"polled"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>boolean</td>
<td class='confluenceTd'>True if this property can only be queried/polled. Default False.</td>
</tr>
<tr>
<td class='confluenceTd'>"reference"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>unknown?</td>
</tr>
<tr>
<td class='confluenceTd'>"parent_ref"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>boolean</td>
<td class='confluenceTd'>True if this property references an object in which this object is in a child-parent relationship. Default False</td>
</tr>
<tr>
<td class='confluenceTd'>"x-"&lt;varies&gt;</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>Any AMQP type</td>
<td class='confluenceTd'>An application-defined attribute.</td>
</tr>
</tbody></table>



<h4><a name="QMFv2APIProposal-SchemaForDescribingMethodCalls"></a>Schema For Describing Method Calls</h4>

<p>SchemaMethod class identifies the method call and describes its<br/>
parameter list.  The parameter list is represented by an unordered<br/>
map of SchemaProperty entries indexed by parameter name.</p>


<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class SchemaMethod:
      &lt;constructor&gt;( [args=&lt;map of "name":&lt;SchemaProperty&gt; entries&gt;],
                     desc="description of the method")
      .getDesc(): return human-readable description of the method.
      .getArgumentCount(): return the number of arguments
      .getArguments(): returns a copy of the map of "name":&lt;SchemaProperty&gt;
      .getArgument("name"): return the SchemaProperty for the parameter "name"

      .addArgument("name", SchemaProperty): adds an additional argument to
                  the parameter list.
      .mapEncode(): returns a map encoding of the SchemaMethod instance
</pre>
</div></div>


<p>The map encoding of a SchemaMethod:</p>

<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>Index</th>
<th class='confluenceTh'>Optional</th>
<th class='confluenceTh'>Type</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"name"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>The name of the method.</td>
</tr>
<tr>
<td class='confluenceTd'>"arguments"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map of "name":&lt;SchemaProperty&gt; values, one for each argument to the method.</td>
</tr>
</tbody></table>


<p>Note that the "dir" SchemaProperty attribute applies to each<br/>
argument.  If "dir" is not specified, it is assumed to be "I".</p>


<h4><a name="QMFv2APIProposal-SchemaforDataObjectsandEvents"></a>Schema for Data Objects and Events</h4>

<p>A top-level data object in QMF is a collection of properties and<br/>
methods. Data objects are described by the class SchemaObjectClass.<br/>
Data objects may be managed (owned) by an Agent.  </p>

<p>Events describe a change within the underlying management system.  Unlike<br/>
data objects, events do not correspond to a managable entity.  They<br/>
are composed of a list of arguments that apply to the event. Event<br/>
objects are described by the class SchemaEventClass. </p>

<p>Both of these classes derive from the virtual base SchemaClass.</p>

<p>Agent applications may dynamically construct instances of these<br/>
objects by adding properties and methods at run time.<br/>
However, once the Schema is published, it must be considered<br/>
immutable, as the hash value must be constant once the Schema is<br/>
in use.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class SchemaClass:
      &lt;constructor&gt;( package=&lt;package-name-str&gt;, 
                     class=&lt;class-name-str&gt;,
                     type=&lt;SchemaTypeData|SchemaTypeEvent&gt;),
                     desc=&lt;description of schema&gt;,
                     hash=&lt;hash-str, format="%08x-%08x-%08x-%08x"&gt;)
      .getClassId(): return the SchemaClassId that identifies this
                      Schema instance.
      .getDesc(): return a human-readable description of this schema.
      .generateHash(): generate a hash over the body of the schema,
                       and return a string representation of the hash
                       in format  "%08x-%08x-%08x-%08x"
      .mapEncode(): returns a map encoding of the instance.


class SchemaObjectClass(SchemaClass):
      &lt;constructor&gt;( package=&lt;package-name-str&gt;, 
                     class=&lt;class-name-str&gt;,
                     type=&lt;SchemaTypeData|SchemaTypeEvent&gt;)
                     hash=&lt;hash-str, format="%08x-%08x-%08x-%08x"&gt;,
                     _properties=&lt;map of "name":SchemaProperty instances&gt;,
                     _methods=&lt;map of "name":SchemaMethod instances&gt;)
      .getPrimaryKeyList(): return an order list of names of the
          property(s) that are used to construct the key for
          identifying unique instances of this class.

      .getPropertyCount(): return the number of data properties.
      .getProperties(): return a map of "name":&lt;SchemaProperty&gt;
          entries for each property in the object.
      .getProperty("name"): return the SchemaProperty associated with "name"

      .getMethodCount(): return the number of methods.
      .getMethods(): return a map of "name":&lt;SchemaMethod&gt; entries for
          each  method in the object.
      .getMethod("name"): return the SchemaMethod associated with the "name"

      .addProperty("name", SchemaProperty): add a new property
      .addMethod("name", SchemaMethod): add a new method.
      .appendPrimaryKey("name"): add "name" to the end of the list of
          names used to construct the primary key.


class SchemaEventClass(SchemaClass):
      &lt;constructor&gt;( package=&lt;package-name-str&gt;, 
                     class=&lt;class-name-str&gt;,
                     type=&lt;SchemaTypeData|SchemaTypeEvent&gt;)
                     hash=&lt;hash-str, format="%08x-%08x-%08x-%08x"&gt;,
                     _properties=&lt;map of "name":SchemaProperty instances&gt;)
      .getPropertyCount(): return the number of data properties.
      .getProperties(): return a map of "name":&lt;SchemaProperty&gt;
          entries for each property in the event.
      .getProperty("name"): return the SchemaProperty associated with "name"

      .addProperty("name", SchemaProperty): add a new property.
</pre>
</div></div>


<p>The map encoding of a SchemaObjectClass:</p>

<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>Index</th>
<th class='confluenceTh'>Optional</th>
<th class='confluenceTh'>Type</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"schema_id"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map containing the SchemaClassId for this object.</td>
</tr>
<tr>
<td class='confluenceTd'>"desc"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>Human-readable description of this Schema.</td>
</tr>
<tr>
<td class='confluenceTd'>"primary_key"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>list</td>
<td class='confluenceTd'>Ordered list of property names used to construct the Primary Key.</td>
</tr>
<tr>
<td class='confluenceTd'>"properties"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map of "name":&lt;SchemaProperty&gt; values, one for each property in the object.</td>
</tr>
<tr>
<td class='confluenceTd'>"methods"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map of "name":&lt;SchemaMethod&gt; values, one for each method in this object.</td>
</tr>
</tbody></table>


<p>The map encoding of a SchemaEventClass:</p>

<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>Index</th>
<th class='confluenceTh'>Optional</th>
<th class='confluenceTh'>Type</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"schema_id"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map containing the SchemaClassId for this object.</td>
</tr>
<tr>
<td class='confluenceTd'>"desc"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>Human-readable description of this Schema.</td>
</tr>
<tr>
<td class='confluenceTd'>"properties"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map of "name":&lt;SchemaProperty&gt; values, one for each property in the event.</td>
</tr>
</tbody></table>



<h3><a name="QMFv2APIProposal-DataModel"></a>Data Model</h3>

<p>The QMF defines three <em>layers</em> of data representation:</p>

<ol>
	<li>arbitrarily structured data</li>
	<li>structured data</li>
	<li>managed structured data</li>
</ol>


<p>Arbitrarily structured data is represented by a map of named data<br/>
items.  There is no schema associated with this data, as its structure<br/>
is arbitrary.  This class of data is represented by the QmfData class.</p>

<p>Data that has a formally defined structure is represented by the<br/>
QmfDescribed class.  This class extends the QmfData class by<br/>
associating the data with a formal schema.</p>

<p>Managed structured data extends the concept of structured data by<br/>
providing an owner for the data.  In QMF, this owner is an Agent.<br/>
This class of data extends the QmfDescribed class by adding an<br/>
identifier of the owning Agent.</p>


<h4><a name="QMFv2APIProposal-QmfDataClass"></a>QmfData Class</h4>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class QmfData:
      &lt;constructor&gt;( map of "name"=&lt;AMQP Type&gt; pairs, _const=False )
      .isManaged(): returns False for QmfData instances.
      .isDescribed(): returns False for QmfData instances.
      .getProperty(name): return the value of the named property
                          data, returns None if named property is
                          not present. 
      .setProperty(name, value): set the value of the named
                             property data.  Creates a new property
                             if the named property does not exist.
                             Raises an exception if _const is True.
      .getProperties(): return a map of "name"=&lt;value&gt; pairs for each
                        of the object's properties. 
      .mapEncode(): returns a map representation of the QmfData
          instance, suitable for use by the constructor.
</pre>
</div></div>

<p>The map encoding of a QmfData Class is merely a map of the<br/>
"name"=&lt;value&gt; property pairs.</p>


<h4><a name="QMFv2APIProposal-QmfDescribedClass"></a>QmfDescribed Class</h4>

<p>The isDescribed() method of an instance of a QmfDescribed class<br/>
returns True.  The SchemaCache allows the QmfDescribed instance to<br/>
retrieve the instance of the schema identified by SchemaClassId (TBD).</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class QmfDescribed(QmfData):
      &lt;constructor&gt;( map of "name"=&lt;AMQP Type&gt; pairs, _const=False,
                     _schemaId=&lt;class SchemaClassId&gt;, SchemaCache )
      &lt;constructor&gt;( map encoding of a QmfDescribed instance, SchemaCache )
      .getSchemaClassId(): returns the identifier of the Schema that
                            describes the structure of the data.
      .getPrimaryKey(): return a string composed from concantenating
            the values of all properties whose names appear in the schema's
            Primary Key List.  Each value is converted to an AMQP
            string, then concantenated in the order given by the
            schema's Primary Key List.
      .mapEncode(): returns a map representation of the QmfDescribed
        instance, suitable for use by the constructor.
</pre>
</div></div>


<p>The map encoding of an instance of the QmfDescribed class:</p>

<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>Index</th>
<th class='confluenceTh'>Optional</th>
<th class='confluenceTh'>Type</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"schema_id"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map containing the SchemaClassId for this object.</td>
</tr>
<tr>
<td class='confluenceTd'>"properties"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map of "name"=&lt;value&gt; pairs, one for each property.</td>
</tr>
</tbody></table>


<h4><a name="QMFv2APIProposal-QmfManagedClass"></a>QmfManaged Class</h4>

<p>The QmfManaged class represents a data object that is owned by a<br/>
particular Agent.  All QmfManaged objects contain a unique object<br/>
identifier. </p>

<p>The isManaged() method of an instance of a QmfManaged class<br/>
returns True.</p>

<p>The QmfManaged class extends the QmfDescribed class:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class QmfManaged(QmfDescribed):
      &lt;constructor&gt;( map of "name"=&lt;AMQP Type&gt; pairs, _const=False,
                     _schemaId=&lt;class SchemaClassId&gt;, SchemaCache,
                     _agentId=&lt;class AgentId&gt; )
      &lt;constructor&gt;( map encoding of a QmfManaged instance, SchemaCache )
      .getObjectId(): return ObjectId for this object.
      .getTimestamps(): returns a list of timestamps describing the
                        lifecycle of the object.  All timestamps are
                        represented by the AMQP timestamp type.
                        [0] = time of last update from Agent,
                        [1] = creation timestamp 
                        [2] = deletion timestamp, or zero if not deleted.
      .isDeleted(): True if deletion timestamp not zero.
</pre>
</div></div>

<p>The map encoding of an instance of the QmfManaged class:</p>

<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>Index</th>
<th class='confluenceTh'>Optional</th>
<th class='confluenceTh'>Type</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"schema_id"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map containing the SchemaClassId for this object.</td>
</tr>
<tr>
<td class='confluenceTd'>"agent_id"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map representation of the AgentId value.</td>
</tr>
<tr>
<td class='confluenceTd'>"properties"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map of "name"=&lt;value&gt; pairs, one for each property.</td>
</tr>
</tbody></table>



<h5><a name="QMFv2APIProposal-ObjectIdentification"></a>Object Identification</h5>

<p>An instance of a managed object must be uniquely identified within the<br/>
management system. This is done by combining an object's Primary Key<br/>
with the identifier for the agent which manages the object.</p>

<p>Each managed object is identified by its Primary Key.  The Primary Key<br/>
is constructed by concatenating the values of the object's properties<br/>
that are named in the schema's Primary Key List.</p>

<p>For example, assume a managed object with the following property map: </p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>{"field1" : "foo",
 "field2" : "bar",
 "field3" : 42,
 "field4" : "baz"}
</pre>
</div></div>

<p>and assume the Primary Key List defined by the managed object's schema<br/>
is: </p>
<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>["field1", "field3"]
</pre>
</div></div>

<p>The Primary Key for this object would be:</p>
<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>"foo:42"
</pre>
</div></div>

<p>QMF inserts ":" between each field.  </p>

<p>Note that these Primary Key values are not unique across Agents.<br/>
Therefore, a globally unique name for an instance of a managed object<br/>
is the concatenation of the object's name and the managing Agent's<br/>
AgentId.  This unique object identifier is represented by the ObjectId class.</p>

<p>ObjectIds are considered <em>hashable</em>.  That is, it is a requirement<br/>
that ObjectIds are deterministically sortable.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class ObjectId:
      &lt;constructor&gt;(agentid=class AgentId, 
                    key="primary key string")
      .getAgentId(): returns AgentId
      .getPrimaryKey(): returns the primary key string.
      .operators(==, !=, &lt;, &gt;) supported.
      .mapEncode(): returns a map representation of the ObjectId.
</pre>
</div></div>

<p>The map encoding of an instance of an ObjectId:</p>

<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>Index</th>
<th class='confluenceTh'>Optional</th>
<th class='confluenceTh'>Type</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"agent_id"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map representation of the AgentId value.</td>
</tr>
<tr>
<td class='confluenceTd'>"primary_key"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>string</td>
<td class='confluenceTd'>The Primary Key string.</td>
</tr>
</tbody></table>



<h4><a name="QMFv2APIProposal-QmfEventClass"></a>QmfEvent Class</h4>

<p>A QMF Event is a type of QmfDescribed data that is not managed.<br/>
Events are notifications that are sent by Agents. <br/>
Unlike QmfManaged objects, events do not correspond to managed data on the<br/>
Agent.  Instead, an event notifies a Console of a change in some<br/>
aspect of the system under management.  For example, an event may<br/>
indicate that a threshold was exceeded, or a resource was returned.<br/>
The structure of an event is described by the SchemaEventClass.  An<br/>
instance of an event is represented by the QmfEvent class.</p>

<p>A timestamp - in milliseconds since midnight, January 1, 1970 UTC - is<br/>
associated with each QmfEvent instance.</p>
<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class QmfEvent(QmfDescribed):
      &lt;constructor&gt;( timestamp, agentId, map of "name"=&lt;AMQP Type&gt; pairs,
                     _const=False, _schemaId=&lt;class SchemaClassId&gt;,
                     SchemaCache )
      &lt;constructor&gt;( map encoding of a QmfEvent instance, SchemaCache )
      .getTimestamp(): return a timestamp indicating when the Event
                       occurred.
      .getAgentId(): return the class AgentId of this issuing agent.
</pre>
</div></div>

<p>The map encoding of an instance of the QmfDescribed class:</p>

<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>Index</th>
<th class='confluenceTh'>Optional</th>
<th class='confluenceTh'>Type</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"schema_id"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map containing the SchemaClassId for this object.</td>
</tr>
<tr>
<td class='confluenceTd'>"timestamp"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>AMQP Timestamp</td>
<td class='confluenceTd'>Time the event occurred.</td>
</tr>
<tr>
<td class='confluenceTd'>"agent_id"</td>
<td class='confluenceTd'>N</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map representation of the AgentId value.</td>
</tr>
<tr>
<td class='confluenceTd'>"properties"</td>
<td class='confluenceTd'>Y</td>
<td class='confluenceTd'>map</td>
<td class='confluenceTd'>Map of "name"=&lt;value&gt; pairs, one for each property.</td>
</tr>
</tbody></table>



<h3><a name="QMFv2APIProposal-DataManagement"></a>Data Management</h3>

<p>The role of a QMF component determines how it will interact with<br/>
managment data. Therefore the QmfManaged class is subclassed<br/>
to provide an Agent specific implementation and a Console specific<br/>
implementation. </p>

<p>The Console application represents a managed data object by the<br/>
QmfConsoleData class.  The Console has "read only" access to the<br/>
properties in the data object via this class.  The<br/>
Console can also invoke the methods defined by the object via this<br/>
class.  The actual data stored in this object is cached from the<br/>
Agent.  In order to update the cached values, the Console invokes the<br/>
refresh() method.</p>

<p>Note that the refresh() and invokeMethod() methods require<br/>
communication with the remote Agent.  As such, they may block.  For<br/>
these two methods, the Console has the option of blocking in the call<br/>
until the call completes.  Optionally, the Console can receive a<br/>
notification asynchronously when the operation is complete.  See below<br/>
for more detail regarding synchronous and asynchronous API calls.</p>


<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class QmfConsoleData(QmfManaged):
      .refresh([reply-handle | timeout]): request that the Agent
                    update the value of this object's contents.
      .invokeMethod(name, inArgs{}, [[reply-handle] | [timeout]]): 
                          invoke the named method.
</pre>
</div></div>



<p>The Agent that manages the data represents it by the<br/>
QmfAgentData class.  The Agent is responsible for managing the<br/>
values of the properties within the object, as well as<br/>
servicing the object's method calls.  Unlike the Console, the Agent<br/>
has full control of the state of the object.</p>


<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class QmfAgentData(QmfManaged):
      .destroy(): mark the object as deleted by setting the deletion
                  timestamp to the current time.
      .setProperty(name, value): update the value of the property.
      .incProperty(name, delta): add the delta to the property
      .decProperty(name, delta): subtract the delta from the property
      ?tbd?
</pre>
</div></div>



<h5><a name="QMFv2APIProposal-InvokingMethods"></a>Invoking Methods</h5>

<p>A managed object's methods provide a mechanisms for a Console application to<br/>
perform a "remote procedure call" against the object.  The method<br/>
actually executes on the Agent, and the result is passed back to the<br/>
Console on completion.</p>

<p>The value(s) returned to the Console when the method call completes<br/>
are represented by the MethodResult class.  The MethodResult class<br/>
indicates whether the method call succeeded or not, and, on success,<br/>
provides access to all data returned by the method call.</p>

<p>Should a method call result in a failure, this failure is indicated by<br/>
the presence of a QmfData object in the MethodResult.  This object is<br/>
described as an "exception", and contains a description of the reason<br/>
for the failure. There are no returned parameters when a method call<br/>
fails. </p>

<p>A successful method invokation is indicated by the absence of an<br/>
exception in the MethodResult.  In addition, a map of returned<br/>
parameter values may be present.  The structure of the returned<br/>
parameters is described by the SchemaMethod for the method.<br/>
The map contains only those parameters that the SchemaMethod marks<br/>
with an "output" direction. </p>


<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class MethodResult:
      &lt;constructor&gt;( QmfData &lt;exception&gt; | &lt;map of properties&gt; )
      .getException(): returns exception data if method fails.
      .getArguments(): returns a map of "name"=&lt;value&gt; pairs
                       of all returned arguments.
      .getArgument(&lt;name&gt;): returns value of argument named "name".
</pre>
</div></div>



<h4><a name="QMFv2APIProposal-Queries"></a>Queries</h4>

<p>A Query is a mechanism for interrogating the management database.  A<br/>
Query represents a selector which is sent to an Agent.  The Agent<br/>
applies the Query against the management database, and <br/>
returns those objects which meet the constraints described in the query.  </p>

<p>Queries are expressed using the following map object:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>{"query": {"what":&lt;target&gt;}
          {"where":&lt;expression selector&gt;}
          ...future selectors....
}
</pre>
</div></div>

<p>Where:</p>

<p><em>&lt;target&gt;</em> indicates the desired object to match.  These are<br/>
implemented as strings containing reserved words that identify the<br/>
target type. The query returns a list of instances of target types<br/>
that match the query. QMF queries will support the following values<br/>
for <em>&lt;target&gt;</em>:</p>

<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>Target</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"schema_id"</td>
<td class='confluenceTd'>Query against the set of schema class identifiers, return<br/>
a list of matched SchemaClassId instances.</td>
</tr>
<tr>
<td class='confluenceTd'>"schema"</td>
<td class='confluenceTd'>Query against class SchemaClass objects, and<br/>
return a list of matched SchemaClass instances.</td>
</tr>
<tr>
<td class='confluenceTd'>"object_id"</td>
<td class='confluenceTd'>Query against an agent's set of known ObjectsIds, return<br/>
a list of matching ObjectId instances.</td>
</tr>
<tr>
<td class='confluenceTd'>"mgt_data"</td>
<td class='confluenceTd'>Query against an agent's set of QmfManaged data, return a<br/>
list of matching QmfManaged instances.</td>
</tr>
<tr>
<td class='confluenceTd'>"agent_id"</td>
<td class='confluenceTd'>Query against all agents within the QMF domain, return a<br/>
list of matching AgentId instances.</td>
</tr>
<tr>
<td class='confluenceTd'>more tbd ...</td>
<td class='confluenceTd'>...</td>
</tr>
</tbody></table>


<p><em>&lt;expression selector&gt;</em> describes a logical match expression<br/>
that is applied to the target.  The expression can be built up from<br/>
the following syntax: </p>


<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>  binary_cmp=["eq", "ne", "lt", "le", "gt", "ge", "re_match"]
  unary_cmp=["present"]

  compare_exp = [binary_cmp, &lt;name&gt;, &lt;value&gt;] | 
                [unary_cmp, &lt;name&gt;]

  n_ary_logic = ["and", "or"]
  unary_logic = ["not"]

  logic_exp = [n_ary_logic, &lt;expression&gt;, &lt;expression&gt;, ... ] | 
              [unary_logic, &lt;expression&gt;] 

  expression = compare_exp | logic_exp
</pre>
</div></div>


<p>In implementation, n_ary operations may be expressed as (n+1)-element<br/>
lists in the format: <span class="error">&#91;&lt;keyword&gt;, &lt;arg1&gt;, &lt;arg2&gt;, ..., &lt;argN&gt;&#93;</span>.  Unary<br/>
operations may be expressed as 2-element lists in the format:<br/>
<span class="error">&#91;&lt;keyword&gt;, &lt;arg1&gt;&#93;</span>  </p>

<p>Examples:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>     the conditional:
    
         "name" == "tross" or ("name" == "jross" 
                                and "address" == "1313 Spudboy Lane"
                                and "town" == "Utopia")

     would be expressed in a python-ish list as:
  
         ["or", ["eq", "name", "tross"],
                ["and", ["eq", "name", "jross"],
                        ["eq", "address", "1313 Spudboy Lane"],
                        ["eq", "town", "Utopia"]]]


     the conditional:

         !(("name" matches regular expression "?ross") and 
           (("age" property is present) or ("status" is not "sleepy")))

     would be:

        ["not", ["and", ["re_match", "name", "?ross"],
                        ["or", ["present", "age"],
                               ["ne", "status", "sleepy"]]]]

</pre>
</div></div>


<p>The valid set of <em>&lt;name&gt;</em> values in the expressions above is determined<br/>
by the <em>&lt;target&gt;</em> supplied in the "what" qualifier.  The tables below<br/>
list the set of name strings allowed for each type of query:</p>


<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>"schema_id" query names</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"package_name"</td>
<td class='confluenceTd'>Compare value against the package name string.</td>
</tr>
<tr>
<td class='confluenceTd'>"class_name"</td>
<td class='confluenceTd'>Compare value against the class name string.</td>
</tr>
<tr>
<td class='confluenceTd'>"type"</td>
<td class='confluenceTd'>Compare value against the schema type strings "data" and<br/>
"event".</td>
</tr>
<tr>
<td class='confluenceTd'>"hash_str"</td>
<td class='confluenceTd'>Compare against the schema's hash string value.</td>
</tr>
</tbody></table>


<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>"schema" query names</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"package_name"</td>
<td class='confluenceTd'>Compare value against the package name string.</td>
</tr>
<tr>
<td class='confluenceTd'>"class_name"</td>
<td class='confluenceTd'>Compare value against the class name string.</td>
</tr>
<tr>
<td class='confluenceTd'>"type"</td>
<td class='confluenceTd'>Compare value against the schema type strings "data" and<br/>
"event".</td>
</tr>
<tr>
<td class='confluenceTd'>"hash_str"</td>
<td class='confluenceTd'>Compare against the schema's hash string value.</td>
</tr>
</tbody></table>


<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>"agent_id" query names</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"vendor"</td>
<td class='confluenceTd'>Compare against the value of the agent's vendor string.</td>
</tr>
<tr>
<td class='confluenceTd'>"product"</td>
<td class='confluenceTd'>Compare against the value of the agent's product string.</td>
</tr>
<tr>
<td class='confluenceTd'>"name"</td>
<td class='confluenceTd'>Compare against the value of the agent's name string.</td>
</tr>
</tbody></table>

<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>"object_id" query names</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"vendor"</td>
<td class='confluenceTd'>Compare against the value of the managing agent's vendor<br/>
string.</td>
</tr>
<tr>
<td class='confluenceTd'>"product"</td>
<td class='confluenceTd'>Compare against the value of the managing agent's product<br/>
string.</td>
</tr>
<tr>
<td class='confluenceTd'>"name"</td>
<td class='confluenceTd'>Compare against the value of the managing agent's name<br/>
string.</td>
</tr>
<tr>
<td class='confluenceTd'>"primary_key"</td>
<td class='confluenceTd'>Compare against the value of the object's primary key<br/>
string.</td>
</tr>
</tbody></table>


<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>"mgt_data" query names</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>"package_name"</td>
<td class='confluenceTd'>Compare value against the package name string.</td>
</tr>
<tr>
<td class='confluenceTd'>"class_name"</td>
<td class='confluenceTd'>Compare value against the class name string.</td>
</tr>
<tr>
<td class='confluenceTd'>"type"</td>
<td class='confluenceTd'>Compare value against the schema type strings "data" and<br/>
"event".</td>
</tr>
<tr>
<td class='confluenceTd'>"hash_str"</td>
<td class='confluenceTd'>Compare against the schema's hash string value.</td>
</tr>
<tr>
<td class='confluenceTd'>"vendor"</td>
<td class='confluenceTd'>Compare against the value of the managing agent's vendor<br/>
string.</td>
</tr>
<tr>
<td class='confluenceTd'>"product"</td>
<td class='confluenceTd'>Compare against the value of the managing agent's product<br/>
string.</td>
</tr>
<tr>
<td class='confluenceTd'>"name"</td>
<td class='confluenceTd'>Compare against the value of the managing agent's name<br/>
string.</td>
</tr>
<tr>
<td class='confluenceTd'>"primary_key"</td>
<td class='confluenceTd'>Compare against the value of the object's primary key<br/>
string.</td>
</tr>
<tr>
<td class='confluenceTd'>"property()"</td>
<td class='confluenceTd'>Specifies the name of  property in the QmfManaged data<br/>
object. The value of the property is used for the comparison.<br/>
Example: property(address) retrieves the value of the property named<br/>
"address"</td>
</tr>
</tbody></table>


<p>a <em>&lt;expression selector&gt;</em> to apply against the<br/>
objects properties.  The name strings of this <em>&lt;expression selector&gt;</em><br/>
are the names of the object's properties.|</p>

<h5><a name="QMFv2APIProposal-ExampleQueries"></a>Example Queries</h5>

<p>With the above syntax, QMF queries can be constructed using AMQP maps<br/>
and lists.</p>

<p>For example, a query for all known schema identifiers:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>    {"query": {"what":"schema_id"}}
</pre>
</div></div>

<p>Note that the absence of a "where" clause acts as a "match all"<br/>
directive. </p>

<p>A query for all schema identifiers whose package names are equal to<br/>
the string "myPackage": </p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>    {"query": {"where": ["eq", "package_name", "myPackage"],
               "what":"schema_id"
              }
    }
</pre>
</div></div>

<p>Query for all SchemaClass objects that match a given package and class<br/>
name:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>    {"query": {"what":"schema",
               "where": ["and", ["eq", "package_name", "myPackage"],
                                ["eq", "class_name", "someClass"]]
              }
    }
</pre>
</div></div>

<p>Query for an ObjectId whose primary key matches a given regular<br/>
expression:</p>
<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>    {"query": {"what":"object_id",
               "where": ["re_match", "primary_key", "foo*"]
              }
    }
</pre>
</div></div>

<p>Query for all QmfManaged objects from a particular schema, whose<br/>
"temperature" property is greater or equal to 50:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>    {"query": {"what":"mgt_data",
               "where": ["and", ["eq", "package_name", "myPackage"],
                                ["eq", "class_name", "someClass"],
                                ["ge", "property(temperature)", 50]
                        ]
              }
    }
</pre>
</div></div>

<p>In the previous example, the agent will convert the value 50 to a<br/>
type compatible with the type given by the "temperature" property's<br/>
schema in order to do the comparison. </p>


<p>Query for all objects that match the given schema, which have a<br/>
property named "state" which does not match the regular expression<br/>
"$Error*", or whose "owner" property is not set to "Cartman".</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>    {"query": {"what":"mgt_data",
               "where": ["and", ["eq", "package_name", "myPackage"],
                                ["eq", "class_name", "someClass"],
                                ["or", ["not", ["re_match", "property(state)", "$Error"]],
                                       ["ne", "property(owner)", "Cartman"]]
                        ]
              }
    }
</pre>
</div></div>





<h4><a name="QMFv2APIProposal-Subscriptions"></a>Subscriptions</h4>

<p>A subscription is a mechanism for monitoring management data for<br/>
changes in its state. A Console creates a subscription with an Agent<br/>
based on a Query.  The Query specifies the set of management data that<br/>
is to be monitored. When the Agent detects changes to the selected<br/>
set, a notification is sent to the subscribing Console(s).  A<br/>
Subscription is represented by the SubscriptionId class.  A Console<br/>
must cancel the subscription when the console no longer needs to<br/>
monitor the data.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class SubscriptionId:
      ?tbd?
</pre>
</div></div>


<h4><a name="QMFv2APIProposal-AgentIdentifiers"></a>Agent Identifiers</h4>

<p>An Agent is uniquely identified by an AgentId.  An AgentId is a<br/>
three-tuple containing:</p>

<ul>
	<li>the name of the vendor that produced the agent</li>
	<li>the name of the product using the agent</li>
	<li>the name of the agent component within the product.</li>
</ul>


<p>This naming convention allows for a single product to host multiple<br/>
distinct Agents.  The Agent identifier is represented by the AgentId<br/>
class. An AgentId is considered <em>hashable</em>.  That is, it is a<br/>
requirement that AgentIds are deterministically sortable.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class AgentId:
      &lt;constructor&gt;( vendor, product, name )
      .getVendor(): return the vendor name string
      .getProduct(): return the product name string
      .getName(): return agent component name string
</pre>
</div></div>

<p>By convention, AgentIds are specified as a string containing the<br/>
vendor, product, and name strings separated by colons ":", in that<br/>
order.  For example: "company.com:OurProduct:AnAgent"</p>


<h2><a name="QMFv2APIProposal-Consoleapplicationmodel"></a>Console application model</h2>

<p>A QMF console component is represented by a Console class.  This class<br/>
is the topmost object of the console application's object model.</p>

<p>A Console is composed of the following objects:</p>

<ul>
	<li>a connection to the AMQP bus</li>
	<li>a queue of inbound work items</li>
	<li>a collection of all known schemas</li>
	<li>a list of all known remote Agents</li>
	<li>a cache of QmfConsoleObject proxies</li>
</ul>


<p>The connection to the AMQP bus is used to communicate with remote<br/>
Agents.  The queue is used as a source for notifications coming from<br/>
remote Agents.</p>

<h3><a name="QMFv2APIProposal-Asychronouseventmodel."></a>Asychronous event model.</h3>

<p>The original QMF API defined a set of callback methods that a Console<br/>
application needed to provide in order to process asynchronous QMF<br/>
events.  Each asynchonous event defined its own callback method.</p>

<p>The new API replaces this callback model with a work-queue approach.<br/>
All asynchronous events are represented by a WorkItem object.  When<br/>
a QMF event occurs it is translated into a WorkItem object and placed<br/>
in a FIFO queue.  It is left to the console application to drain<br/>
this queue as needed.</p>

<p>This new API does require the console application to provide a single<br/>
callback.  The callback is used to notify the console application that<br/>
WorkItem object(s) are pending on the work queue.  This callback is<br/>
invoked by QMF when the work queue transitions from the empty to the<br/>
non-empty state.  To avoid any potential threading issues, the console<br/>
application is <em>not</em> allowed to call any QMF API from within the<br/>
callback context.  The purpose of the callback is to allow the console<br/>
application to schedule itself to drain the work queue at the next<br/>
available opportunity.  </p>

<p>For example, a console application may be designed using a <tt>select()</tt><br/>
loop.  The application waits in the <tt>select()</tt> for any of a number<br/>
of different descriptors to become ready.  In this case, the callback<br/>
could be written to simply make one of the descriptors ready, and then<br/>
return.  This would cause the application to exit the wait state, and<br/>
start processing pending events.</p>

<p>The callback is represented by the Notifier virtual base class.  This<br/>
base class contains a single method.  A console application derives a<br/>
custom handler from this class, and makes it available to the Console<br/>
object. </p>


<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class Notifier:
    .indication():  Called when the internal work queue becomes
    non-empty due to the arrival of one or more WorkItems. This method
    will be called by the internal QMF management thread - it is
    illegal to invoke any QMF APIs from within this callback.  The
    purpose of this callback is to indicate that the application
    should schedule itself to process the work items.  
</pre>
</div></div>


<p>The WorkItem class represents a single notification event that is read<br/>
from the work queue:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class WorkItem:
    #
    # Enumeration of the types of WorkItems produced by the Console
    #
    AGENT_ADDED = 1
    AGENT_DELETED = 2
    NEW_PACKAGE = 3
    NEW_CLASS = 4
    OBJECT_UPDATE = 5
    EVENT_RECEIVED = 7
    AGENT_HEARTBEAT = 8

    .getType(): Identifies the type of work item by returning one of
    the above type codes. 

    .getHandle(): return the handle for an asynchronous operation, if present.

    .getParams(): Returns the data payload of the work item.  The type
    of this object is determined by the type of the workitem (?TBD?). 
</pre>
</div></div>


<h3><a name="QMFv2APIProposal-LocalrepresentationofaremoteAgent."></a>Local representation of a remote Agent.</h3>

<p>The console application maintains a list of all known remote Agents.<br/>
Each Agent is represented by the Agent class:</p>


<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class Agent:
      &lt;constructor&gt;( AgentId )
      .getName(): returns the AgentId
      ?tbd?
</pre>
</div></div>


<h3><a name="QMFv2APIProposal-TheConsoleObject."></a>The Console Object.</h3>

<p>The Console class is the top-level object used by a console<br/>
application.  All QMF console functionality is made available by this<br/>
object.  A console application must instatiate one of these objects.</p>

<p>As noted below, some Console methods require contacting a remote<br/>
Agent.  For these methods, the caller has the option to either block<br/>
for a (non-infinite) timeout waiting for a reply, or to allow the<br/>
method to complete asynchonously.  When the asynchronous approach is<br/>
used, the caller must provide a unique handle that identifies the<br/>
request.  When the method eventually completes, a WorkItem will be<br/>
placed on the work queue.  The WorkItem will contain the handle that<br/>
was provided to the corresponding method call.</p>

<p>All blocking calls are considered thread safe - it is possible to have<br/>
a multi-threaded implementation have multiple blocking calls in flight<br/>
simultaineously.</p>

<p>If a name is supplied, it must be unique across all Consoles attached<br/>
to the AMQP bus.  If no name is supplied, a unique name will be<br/>
synthesized in the format: "qmfc-&lt;hostname&gt;.&lt;pid&gt;"</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class Console:
      &lt;constructor&gt;(name=&lt;name-str&gt;, 
                    notifier=&lt;class Notifier&gt;,
                    timeout=&lt;default for all blocking calls&gt;,
                    subscription_duration=&lt;default lifetime of a subscription&gt;)

      .destroy(timeout=None): Must be called to release Console's resources.

      .addConnection(QPID Connection): Connect the console to the AMQP cloud.

      .removeConnection(conn): Remove the AMQP connection from the
          console.  Un-does the addConnection() operation, and
          releases any agents associated with the connection.  All
          blocking methods are unblocked and given a failure status.
          All outstanding asynchronous operations are cancelled
          without producing WorkItems.

      .getAddress():
          Get the AMQP address this Console is listening to (type str).

      .findAgent( class AgentId, [timeout | handle] ): Query for the
      presence of a specific agent. Returns a class Agent if the agent
      is present.  May be called blocking (with default timeout
      override), or may allow asynchronous completion (producing a
      WorkItem with the given handle).

      .enableAgentDiscovery(): Called to enable the asynchronous
          Agent Discovery process. Once enabled, AGENT_ADD work items
          can arrive on the WorkQueue.

      .disableAgentDiscovery(): Called to disable the async Agent
      Discovery process enabled by calling enableAgentDiscovery().  

      .getWorkItemCount(): Returns the count of pending WorkItems that
      can be retrieved. 

      .getNextWorkItem([timeout=0]): Obtains the next pending work
      item, or None if none available. 

      .releaseWorkItem(wi): Releases a WorkItem instance obtained by
      getNextWorkItem(). Called when the application has finished
      processing the WorkItem. 

      .getAgents(): Returns a list of available agents (class Agent)

      .getAgent( class AgentId ): Return the class Agent for the
          named agent, if known. 

      .getPackages( [class Agent] ): Returns a list of the names of
          all known packages.  If an optional Agent is provided, then
          only those packages available from that Agent are returned.

      .getClasses( [class Agent] ):  Returns a list of SchemaClassIds
          for all available Schema.  If an optional Agent is provided,
          then the returned SchemaClassIds are limited to those
          Schema known to the given Agent.

      .getSchema( class SchemaClassId [, class Agent]): Return a list
          of all available class SchemaClass across all known agents.
          If an optional Agent is provided, restrict the returned
          schema to those supported by that Agent.

      .makeObject( SchemaClassId, **kwargs ): returns an uninitialized
      instance of a QmfDescribed data object.

      .getObjects( _SchemaClassId= | _package=, _class= |
                    _objectId=,
                   [timeout=],
                   [list-of-class-Agent] ): perform a blocking query
           for QmfConsoleObjects.  Returns a list (possibly empty) of matching
           objects. The selector for the query may be either:
           * class SchemaClassId - all objects whose schema match the
                    schema identified by _SchemaClassId parameter.
           * package/class name - all objects whose schema are
                    contained by the named package and class.
           * the object identified by _objectId
           This method will block until all known agents reply, or the
                    timeout expires. Once the timeout expires, all
                    data retrieved to date is returned.  
           If a list of agents is supplied, then the query is sent to
                    only those agents.  


      .createSubscription( class Query [, duration=&lt;secs&gt; [, list of agents] ): creates a
            subscription using the given Query.  If a list of agents
            is provided, the Query will apply only to those agents.
            Otherwise it will apply to all active agents, including
            those discovered during the lifetime of the subscription.
            The duration argument can be used to override the
            console's default subscription lifetime for this
            subscription.  Returns a class SubscriptionId.

      .refreshSubscription( SubscriptionId [, duration=&lt;secs&gt;] ):
      (re)activates a subscription.  Uses the console default duration
      unless the duration is explicitly specified.

      .cancelSubscription( SubscriptionId ): terminates the
      subscription. 
</pre>
</div></div>


<h2><a name="QMFv2APIProposal-ExampleConsoleApplication"></a>Example Console Application</h2>

<p>The following pseudo-code performs a blocking query for a particular agent.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>logging.info( "Starting Connection" )
conn = Connection("localhost")
conn.connect()

logging.info( "Starting Console" )
myConsole = Console()
myConsole.addConnection( conn )

logging.info( "Finding Agent" )
myAgent = myConsole.findAgent( AgentId( "aCompany.com", "Examples", "anAgent" ), _timeout=5 )

if myAgent:
   logging.info( "Agent Found: %s" % myAgent )
else:
   logging.info( "No Agent Found!")

logging.info( "Removing connection" )
myConsole.removeConnection( conn )

logging.info( "Destroying console:" )
myConsole.destroy( _timeout=10 )
</pre>
</div></div>


<p>The following pseudo-code performs a non-blocking query for all<br/>
agents.  It completes when at least one agent is found.</p>


<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class MyNotifier(Notifier):
    def __init__(self, context):
        self._myContext = context
        self.WorkAvailable = False

    def indication(self):
        print("Indication received! context=%d" % self._myContext)
        self.WorkAvailable = True

noteMe = MyNotifier( 668 )

logging.info( "Starting Connection" )
conn = Connection("localhost")
conn.connect()

myConsole = Console(notifier=noteMe)
myConsole.addConnection( conn )

myConsole.enableAgentDiscovery()
logging.info("Waiting...")


while not noteMe.WorkAvailable:
    print("No work yet...sleeping!")
    time.sleep(1)


print("Work available = %d items!" % myConsole.getWorkItemCount())
wi = myConsole.getNextWorkitem(timeout=0)
while wi:
    print("work item %d:%s" % (wi.getType(), str(wi.getParams())))
    wi = myConsole.getNextWorkitem(timeout=0)


logging.info( "Removing connection" )
myConsole.remove_connection( conn )

logging.info( "Destroying console:" )
myConsole.destroy( 10 )
</pre>
</div></div>


<h2><a name="QMFv2APIProposal-AgentApplicationModel"></a>Agent Application Model</h2>

<p>A QMF agent component is represented by a instance of the Agent<br/>
class.  This class is the topmost object of the agent application's<br/>
object model.  Associated with a particular agent are</p>

<ul>
	<li>the set of objects managed by that agent</li>
	<li>the schema that describes the objects owned by the agent</li>
	<li>a collection of consoles that are interfacing with the agent</li>
</ul>


<p>The Agent class communicates with the application using the same<br/>
notification-based model as the console.  The agent maintains a<br/>
work-queue of pending requests.  Each pending request is<br/>
associated with a handle.  When the application is done servicing the<br/>
work request, it passes the response to the agent along with the<br/>
handle associated with the originating request.</p>

<p>An agent can support one of two different models for managing its<br/>
object database: internal or external store.</p>

<h3><a name="QMFv2APIProposal-InternalObjectStore"></a>Internal Object Store</h3>

<p>An agent that implements internal object store takes full<br/>
responsibility for managing its associated objects.  In this model,<br/>
the application passes a reference for each managed objects to the<br/>
agent.  The agent manages the set of objects internally, directly<br/>
accessing the contents of the object in order to service console<br/>
requests.  </p>

<p>With this model, the application's complexity is reduced.  The<br/>
application needs to instantiate the object and register it with the<br/>
agent.  The application also maintains a reference to the object, as<br/>
the application is responsible for updating the object's properties<br/>
as necessary.</p>

<p>The application must also service method calls invoked on the object.<br/>
The agent notifies the application when a method call has been<br/>
requested by a console.  The application services the method call,<br/>
passing the result of the method back to the agent. The agent then<br/>
relays the response to the originating console.</p>

<p>The application may decide to delete an object instance.  The<br/>
application does this by invoking the destroy() method on the object.<br/>
This notifies the agent, which will mark the object as deleted in its<br/>
database. Once the application invokes the destroy() method on an<br/>
object, it must no longer access the object.  The agent will clean up<br/>
the object at a later point in time.</p>

<p>Internal object store is the default model for agent object<br/>
managment.</p>

<h4><a name="QMFv2APIProposal-DataConsistency"></a>Data Consistency</h4>

<p>The internal object store requires sharing of the managed data<br/>
between the agent and the application.  The application is responsible<br/>
for keeping the data up to date, while the agent is responsible for<br/>
providing the data to client consoles.  It is likely that these<br/>
components may be implemented in separate execution contexts. This<br/>
raises the possibility that a data item could be in the process of<br/>
being written to by the application at the same moment the agent<br/>
attempts to read it.  This would result in invalid data being read.  </p>

<p>To prevent this from occuring, the QmfAgentObject class provides<br/>
accessors for all property data in the object. These accessors provide<br/>
atomic access to the underlying data.  Therefore, both the agent and<br/>
the application code <b>must</b> use these accessors to manipulate a shared<br/>
object's data. </p>


<h3><a name="QMFv2APIProposal-ExternalObjectStore"></a>External Object Store</h3>

<p>An alternate agent implementation allows the application to take<br/>
full responsibility for managing the objects.  With this model, all<br/>
instances of managed objects exist external to the agent. When a<br/>
console requests an action against an object, that action is<br/>
transferred from the agent to the application.  The application then<br/>
must process the request, and send the result to the agent.  The agent<br/>
then sends a reply to the requesting console.</p>

<p>The model gives full control of the managed objects to the<br/>
application, but usually requires more application development work. </p>


<h3><a name="QMFv2APIProposal-AgentClass"></a>Agent Class</h3>

<p>The base class for the agent object is the Agent class.  This base<br/>
class represents a single agent implementing internal store.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class Agent:
      &lt;constructor&gt;( vendor=&lt;vendor-string&gt;,
                     product=&lt;product-string&gt;,
                     name=&lt;name-string&gt;,
                     class Notifier ): the vendor/product/name
            combination must uniquely identify this agent instance
            within the QMF domain.  The Notifier is used to alert the
            application to incoming method requests.
      .getAgentId(): return the agent identifier
      .setConnection( QPID Connection ): connect the agent to the AMQP cloud.
      .registerObjectClass( class SchemaObjectClass ): Register a
            schema for an object class with the agent.  The agent must
            have a registered schema for an object class before it can
            handle objects of that class.
      .registerEventClass( class SchemaEventClass ) : Register a
            schema for an event class with the agent.  The agent must
            have a registered schema for an event class before it can
            handle events of that class.
      .raiseEvent( class QmfEvent ): Cause the agent to raise the
            given event.
      .addObject( class QmfManaged, object_name=&lt;object name str&gt; ):
            passes a reference to an instance of a managed QMF object
            to the agent. The object name must uniquely identify this
            object among all objects known to this agent.  Returns a
            class ObjectId containing the object identifier. 
      .getWorkItemCount(): Returns the count of pending WorkItems that
            can be retrieved.
      .getNextWorkItem([timeout=0]): Obtains the next pending work
            item, or None if none available. 
      .releaseWorkItem(wi): Releases a WorkItem instance obtained by
            getNextWorkItem(). Called when the application has finished
            processing the WorkItem. 
      .methodResponse( handle=&lt;handle from WorkItem&gt;,
                       [output argument list],
                       result=&lt;status code&gt;,
                       exception=&lt;QmfData&gt; ): Indicate to the agent
            that the application has completed processing a method
            request. A result code of zero indicates success.  If the
            result code is non-zero, exception may optionally be set to a
            QmfData object that describes the failure.  On success, zero or
            more output arguments may be supplied as defined by the method's
            schema. 

</pre>
</div></div>


<h3><a name="QMFv2APIProposal-AgentExternalClass"></a>AgentExternal Class</h3>


<p>The AgentExternal class must be used by those applications that<br/>
implement the external store model. The AgentExternal class extends<br/>
the Agent class by adding interfaces that notify the application when<br/>
it needs to service a request for management operations from the<br/>
agent.</p>


<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>class AgentExternal(Agent):
      &lt;constructor&gt;(vendor, product, name, class Notifier)
      .allocObjectId( name="object name"): create a class ObjectId
            using the object name provided.  The object name must be
            unique across all objects known to this agent.  Returns a
            class ObjectId.  Once this method returns, the agent will
            service requests from consoles referencing this ObjectId.
      .freeObjectId( class ObjectId ): releases the ObjectId
            previously allocated by allocObjectId() method.  Once
            this call is complete, the agent will reject all further
            requests from consoles referencing this ObjectId.
      .queryResponse( handle=&lt;handle from WorkItem&gt;,
                      class QmfAgentObject): send a managed object in 
            reply to a received query. Note that ownership of the
            object instance is returned to the caller on return from
            this call. 
      .queryComplete( handle=&lt;handle from WorkItem&gt;, 
                      result=&lt;status code&gt; ):  Indicate to the agent
            that the application has completed processing a query request.
            Zero or more calls to the queryResponse() method should be
            invoked before calling queryComplete().  If the query should
            fail - for example, due to authentication error - the result
            should be set to a non-zero error code ?TBD?.
      .subscriptionResponse( handle=&lt;handle from WorkItem&gt;,
                             result=&lt;status code&gt;,
                             subscription_handle=&lt;application context&gt;):
            Indicate the status of a subscription request.  If result
            is zero, the subscription is accepted by the application,
            and an subscription handle is provided.  This handle must
            be passed to the application when the agent unsubscribes.
</pre>
</div></div>


<h3><a name="QMFv2APIProposal-Asychronouseventmodel."></a>Asychronous event model.</h3>


<p>The Agent uses the same notification driven work queue model as the<br/>
Console.  In the Agent case, the WorkItem supports the following set<br/>
of work types: </p>

<ul>
	<li>METHOD_CALL</li>
	<li>QUERY</li>
	<li>SUBSCRIBE</li>
	<li>UNSUBSCRIBE</li>
</ul>


<p>In the case of an internal store agent implementation, only the<br/>
METHOD_CALL work item is generated.  An external store agent must support<br/>
all work item types.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>  METHOD_CALL parameters: ( name=&lt;method name&gt;, 
                            [argument list],
                            class ObjectId,
                            user_id=&lt;authenticated id of the user&gt; )
</pre>
</div></div>

<p>The METHOD_CALL WorkItem describes a method call that must be serviced by the<br/>
application on behalf of this agent.  On completion of the<br/>
method call, the application must invoke the agent's<br/>
methodResponse() method.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>  QUERY parameters: ( class Query, 
                      user_id=&lt;authenticated id of the user&gt; )

</pre>
</div></div>

<p>The QUERY WorkItem describes a query that the application must<br/>
service. The application should call the queryResponse() method for<br/>
each object that satisfies the query.  When complete, the application<br/>
must call the queryComplete() method.  If a failure occurs, the<br/>
application should indicate the error to the agent by calling the<br/>
queryComplete() method with a description of the error.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>  SUBSCRIBE parameters: ( class Query, 
                          user_id=&lt;authenticated id of the user&gt; )
</pre>
</div></div>

<p>The SUBSCRIBE WorkItem provides a query that the application should<br/>
monitor until the subscription is cancelled.  On receipt of this<br/>
WorkItem, the application should call the subscriptionResponse() agent<br/>
method to acknowledge the response.  Whenever the matching objects are<br/>
updated, the application should call queryResponse() for each updated<br/>
object, followed by a call to queryComplete() when done.  The<br/>
subscription remains in effect until an UNSUBSCRIBE WorkItem for the<br/>
subscription is received. </p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>  UNSUBSCRIBE parameters: ( &lt;handle associated with subscription&gt; )
</pre>
</div></div>

<p>Alerts the application that the corresponding subscription has been<br/>
cancelled.  The application should no longer supply query updates<br/>
against the subscribed query.</p>



<h2><a name="QMFv2APIProposal-RevisionHistory"></a>Revision History</h2>


<p>11/20/2009 - First Revision<br/>
11/24/2009 - Added agent classes<br/>
12/01/2009 - Cleaned up Schema api and added map definitions.<br/>
12/04/2009 - Removed distinction between properties and statistics.<br/>
             Object identification now based on Primary Key List.<br/>
             Added more map definitions.<br/>
12/11/2009 - Formally define query implementation.</p>

<h2><a name="QMFv2APIProposal-TodoList"></a>Todo List</h2>

<ul>
	<li>validation of described object vs its schema</li>
	<li>verify that schema's primary keys are present in the defined properties</li>
	<li>make schemas "const" once register with the agent</li>
</ul>


     </div>
     <div id="commentsSection" class="wiki-content pageSection">
       <div style="float: right;">
            <a href="http://cwiki.apache.org/confluence/users/viewnotifications.action" class="grey">Change Notification Preferences</a>
       </div>

       <a href="http://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal">View Online</a>
       |
       <a href="http://cwiki.apache.org/confluence/pages/diffpagesbyversion.action?pageId=5965629&revisedVersion=5&originalVersion=4">View Change</a>
              |
       <a href="http://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal?showComments=true&amp;showCommentArea=true#addcomment">Add Comment</a>
            </div>
</div>
</div>
</div>
</div>
</body>
</html>

---------------------------------------------------------------------
Apache Qpid - AMQP Messaging Implementation
Project:      http://qpid.apache.org
Use/Interact: mailto:commits-subscribe@qpid.apache.org


Mime
View raw message