remove simplejson dependency
[plcapi.git] / doc / PLCAPI.xml.in
index ffc0057..79fb1a8 100644 (file)
@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- -*-xml-*- -->
 <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
 "@DOCBOOK-43@" [
 <!ENTITY Methods SYSTEM "Methods.xml">
     <section id="Filters">
       <title>Filters</title>
 
-      <para>Most of the <function>Get</function> functions take a
+      <para>Most of the <function>Get</function> methods take a
       filter argument. Filters may be arrays of integer (and sometimes
       string) identifiers, or a struct representing a filter on the
-      attributes of the entities being queried. For example,</para>
+      attributes of the entities being queried. For example,
 
 <programlisting>
-# plcsh code fragment (see below)
-GetNodes([1,2,3])
-GetNodes({'node_id': [1,2,3]})
+>>> GetNodes([1,2,3])
+>>> GetNodes({'node_id': [1,2,3]})
 </programlisting>
+</para>
 
       <para>Would be equivalent queries. Attributes that are
-      themselves arrays (such as <literal>nodenetwork_ids</literal>
+      themselves arrays (such as <literal>interface_ids</literal>
       and <literal>slice_ids</literal> for nodes) cannot be used in
       filters.</para>
 
       <para> Filters support a few extra features illustrated in the following examples.</para>
+      
+      <section id="pattern-matching">
+       <title> Pattern Matching</title>
+       <para> <literal>*</literal> can be used in a text value and have the usual meaning, so all nodes in the <emphasis>fr</emphasis> can be obtained with:
+         <programlisting>GetNodes ( { 'hostname' : '*.fr' } ) </programlisting>
+       </para>
+      </section>
+
+      <section id="negation">
+       <title> Negation </title>
+       <para> Fields starting with a <literal>~</literal> are negated, so non-local nodes can be fetched with:
+       <programlisting>GetNodes( { '~peer_id' : None } ) </programlisting>
+       </para>
+      </section>
+
+      <section id="numeric">
+       <title> Numeric comparisons </title>
+       <para> Strictly greater/smaller operations are achieved by prepending the field name like in:
+       <programlisting>GetEvents( { '>time' : 1178531418 } ) </programlisting>
+       </para>
+       <para> Greater/smaller or equal: 
+       <programlisting>GetEvents( { ']event_id' : 2305 } ) </programlisting>
+       </para>
+      </section>
+
+      <section id="sequence">
+       <title> Filtering on a sequence field </title>
+       <para> A field starting with '&amp;' or '|' should refer to a sequence type;
+      the semantics is then that the object's value (expected to be a list)
+      should contain all (&amp;) or any (|) value specified in the corresponding
+      filter value. 
+      <programlisting> GetPersons ( { '|role_ids' : [ 20, 40 ] } ) </programlisting>
+      <programlisting> GetPersons ( { '|roles' : ['tech', 'pi'] } ) </programlisting>
+      <programlisting> GetPersons ( { '&amp;roles' : ['admin', 'tech'] } ) </programlisting>
+      <programlisting> GetPersons ( { '&amp;roles' : 'tech' } ) </programlisting>
+       </para>
+      </section>
+
+      <section id="sort-clip">
+       <title> Sorting and Clipping </title> 
+       <para> The following 3 special fields can be used to extract only a subset of the results for pagination:
+         <programlisting> GetNodes( { '-SORT' : 'hostname' , '-OFFSET' : 30 , '-LIMIT' : 25 }</programlisting>
+       </para>
+      </section>
+    </section>
+
+    <section id="and-or">
+      <title> All criteria / Any criteria </title>
+      <para> The default in the vast majority of the code is to select
+      objects that match ALL the criteria specified in the struct. It
+      is possible to search for objects that match ANY of these by
+      adding the special '-OR' key (the value is then ignored), as in:
+      <programlisting> GetPersons ( { '-OR' : 'anything', 'site_id':2, '&amp;roles':['admin'] } ) </programlisting>
+      </para>
+    </section>
+
+    <section id="tags">
+      <title>Tags</title>
+
+      <para> The PLC API comes with a feature called
+      <emphasis>tags</emphasis>, that basically aims at supporting an
+      extensible data model. A few classes (as of this writing, Nodes,
+      Interfaces, Sites, Persons and Slices) are eligible for being dynamically
+      extended beyond the basic set of fields that are built into the
+      database schema.</para>
+
+      <para> Historically, this is a generalization of the concept of
+      <emphasis> SliceAttribute </emphasis>, and the more recent
+      concept of <emphasis> InterfaceSetting </emphasis>, that with
+      release 5.0 have been renamed into <emphasis> SliceTag
+      </emphasis> and <emphasis> InterfaceTag </emphasis>,
+      respectively. </para>
+
+      <section id="tags-low-level">
+       <title> Low level </title>
+       <para> The low level interface to tags relies on the following items:
       <itemizedlist>
+       <listitem><para> 
+         A <emphasis> TagType </emphasis> object basically models a
+         new column that needs to be added to other objects. In much
+         the same way as nodes are named through a <emphasis>
+         hostname </emphasis>, tagtypes are named with a
+         <emphasis>tagname</emphasis>, plus additional information
+         (<emphasis>category</emphasis>,
+         <emphasis>description</emphasis>).  
+       </para> </listitem>
+
+       <listitem><para>
+         <emphasis>description</emphasis> is mostly informative, it
+           is used by the web interface to provide more details on
+           the meaning of that tag. 
+       </para> </listitem>
+
        <listitem>
-         <para> <emphasis> Pattern Matching </emphasis> </para>
-         <programlisting>GetNodes ( { 'hostname' : '*.fr' } ) </programlisting>
+         <para>
+         <emphasis>category</emphasis> is used in a variety of ways,
+         in the web interface again.  Over time this has become a
+         means to attach various information to a tag type, so it is
+         used as some sort of a poorman's tag tag system :).
+        </para>
        </listitem>
+
        <listitem>
-         <para> <emphasis> Negation </emphasis> </para>
-         <programlisting>GetNodes( { '~peer_id' : None } ) </programlisting>
+       <para>
+          The convention is to set in category a set of slash-separated
+          fields, like the following real examples demonstrate.
+<programlisting> 
+>>> tagnames=['arch','fcdistro','hrn','hmac','exempt_node_until']
+>>> for tt in GetTagTypes(tagnames,['tagname','category']): 
+>>> ... print "tagname=%-18s category=%s"%(tt['tagname'], tt['category'])
+tagname=hrn                category=node/sfa
+tagname=hmac               category=slice/auth  
+tagname=exempt_node_until  category=node/myops
+tagname=fcdistro           category=node/slice/config/ui/header=f/rank=w
+tagname=arch               category=node/slice/config/ui/header=A/rank=x
+</programlisting>
+        </para>
        </listitem>
+
+       <listitem> <para> <emphasis>roles</emphasis> may also be
+       attached to a given tag_type (use AddRoleToTagType or
+       DeleteRoleFromTagType). This is an evolution over the former
+       system based on so-called 'min_role_id', and now any set of
+       roles may be mentioned. More importantly, each type (Node,
+       Person, ...) implements its own policy to let or not non-admin
+       callers change their tags. For example in the current
+       implementation, non-admin users can only change their own
+       person tags. See PLC/AuthorizeHelpers.py for that code.
+       </para> </listitem>
+
        <listitem>
-         <para> <emphasis> Numeric comparisons </emphasis> </para>
-         <programlisting>GetEvents( { '>time' : 1178531418 } ) </programlisting>
-         <programlisting>GetEvents( { ']event_id' : 2305 } ) </programlisting>
+         <para> The low-level method for managaing tags is then, once
+         the TagType is known to the system, to attach a value to,
+         say, a Node, by calling <emphasis> AddNodeTag </emphasis>,
+         and then as usual change this value with <emphasis>
+         UpdateNodeTag </emphasis>, or delete it with <emphasis>
+         DeleteNodeTag </emphasis>. </para>
        </listitem>
+      </itemizedlist>
+    </para>
+      </section>
+
+      <section id="accessors">
+       <title> Accessors </title>
+      <para> A rather more convenient way to use tags is through
+      Accessors. This convenience is located in <emphasis>
+      PLC/Accessors </emphasis>, and allows you to easily define Get
+      or Set methods dedicated to a given tag. This is for instance
+      how the <emphasis> GetNodeArch </emphasis> and <emphasis>
+      SetNodeArch </emphasis> methods are implemented. These methods
+      greatly simplify tags manipulation as they take care of
+      <itemizedlist>
        <listitem>
-         <para> <emphasis> Sorting and Clipping </emphasis> </para>
-         <programlisting> GetNodes( { '-SORT' : 'hostname' , '-OFFSET' : 30 , '-LIMIT' : 25 }</programlisting>
+         <para> Creating and enforcing <emphasis> TagTypes
+         </emphasis>; each time you restart your plc, the tag_types
+         mentioned in accessor definitions are created and checked
+         (in terms of the category, description and roles defined in
+         the various calls to define_accessors).</para>
+       </listitem>
+       <listitem>
+         <para> Create or update the, say, <emphasis> NodeTag
+         </emphasis> object, as needed.</para>
+       </listitem>
+       <listitem> <para> In addition, an accessor definition mentions
+       <emphasis> get_roles </emphasis> (defaults to all_roles), and
+       <emphasis>set_roles </emphasis>. These values are used as
+       follows. <emphasis> get_roles </emphasis> is attached to the
+       Get accessor, so callers that do not have this role cannot run
+       the Get accessor. <emphasis> set_roles </emphasis> is attached
+       to the Set accessor, as well as to the corresponding TagType,
+       which in turn is used for checking write access to the tag
+       type. </para>
        </listitem>
       </itemizedlist>
+    </para>
+
+      <para> <emphasis> Site-specific </emphasis> accessors can be
+      defined in <emphasis>
+      /usr/share/plc_api/PLC/Accessors/Accessors_site.py </emphasis>
+      and will be preserved across updates of the
+      <emphasis>plcapi</emphasis> rpm.
+      </para>
+
+      <para> 
+       The accessors mechanism does not currently support setting slice
+       tags that apply only on a given node or nodegroup. 
+      </para>
+      </section>
+
+      <section id="expose-in-api">
+       <title> Through regular Add/Get/Update methods </title>
+      <para> 
+       Finally, tags may also get manipulated through the
+       <emphasis>AddNode</emphasis>, <emphasis>GetNodes</emphasis>,
+       and <emphasis>UpdateNode</emphasis> methods:
+
+      <itemizedlist>
+       <listitem> <para> 
+         The <literal>define_accessors</literal> function in the
+         Accessors factory has an optional argument named <literal>
+         expose_in_api </literal>. When this is set, the
+         corresponding tag becomes visible from the Add/Get/Update
+         methods almost as if it was a native tag.
+       </para> </listitem>
+
+       <listitem><para>
+         So for instance the following code would be legal and do as expected:
+<programlisting>
+# create a x86_64 node
+>>> AddNode({'hostname':'pl1.foo.com','arch':'x86_64'})
+# get details for pl1.foo.com including tag 'arch' tag
+>>> GetNodes(['pl1.foo.com'],['boot_state','node_type','arch'])
+# set the 'deployment' tag
+>>> UpdateNode('pl1.foo.com',{'deployment':'beta'})
+# get all alpha and beta nodes
+>>> GetNodes({'deployment':'*a'},['hostname','deployment'])
+</programlisting>
+       </para></listitem>
+
+       <listitem><para> 
+         The current limitations about tags, as opposed to native
+         fields, is that for performance, tags won't get returned
+         when using the implicit set of columns. So for instance:
+<programlisting>
+# get all details for 'pl1.foo.com' 
+>>> node=GetNodes(['pl1.foo.com'])[0]
+# this did not return the 'arch' tag
+>>> 'arch' in node
+False
+</programlisting>
+       </para></listitem>
+
+       <listitem><para>
+         For a similar reason, any tag used in the filter argument will <emphasis>have to</emphasis> be mentioned in the list of returned columns as well. For example:
+<programlisting>
+# if 'hrn' is not part of the result, this does not work
+>>> ns=GetNodes({'hrn':'ple.*'},['hostname'])
+Database error b59e068c-589a-4ad5-9dd8-63cc38f2a2eb:
+column "hrn" does not exist
+LINE 1: ...M view_nodes WHERE deleted IS False AND (True AND hrn ILIKE ...
+... abridged ...
+# this can be worked around by just returning 'hrn' as well
+>>> ns=GetNodes({'hrn':'ple.*'},['hrn','hostname'])
+</programlisting>
+       </para></listitem>
+
+      </itemizedlist>
+    </para>
+      </section>
     </section>
 
-    <section>
+    <section id="nodegroups">
+    <title>Nodegroups</title>
+
+    <para> In earlier versions up to v4.2, <emphasis> NodeGroups
+    </emphasis> used to be defined extensively. So you would,
+    basically, create an empty nodegroup instance, and then use
+    <emphasis> AddNodeToNodeGroup </emphasis> or <emphasis>
+    DeleteNodeFromNodeGroup </emphasis> to manage the nodegroup's
+    contents. </para>
+
+    <para> The new model has been redefined as follows. You now define
+    a nodegroup as the set of nodes for which a given <emphasis> Tag
+    </emphasis> has a given value, which are defined once and for good
+    when creating the <emphasis> NodeGroup </emphasis> object. </para>
+
+    <para> So for instance for managing the set of nodes that are
+    running various levels of software code, PLC has defined two
+    <emphasis> NodeGroups </emphasis> named <literal> alpha </literal>
+    and <literal> beta </literal>. With the new model, we would now do
+    something like the following, using the built-in <literal>
+    deployment </literal> tag that is created for that purpose:
+<programlisting>
+### creating node groups
+>>> AddNodeGroup('alphanodes','deployment','alpha')
+21
+>>> AddNodeGroup('betanodes','deployment','beta')
+22
+### checking contents (no node has 'deployment' set to either 'alpha' or 'beta' yet)
+>>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
+{'groupname': u'alphanodes', 'node_ids': []}
+{'groupname': u'betanodes', 'node_ids': []}
+
+### displaying node ids 
+>>> for n in GetNodes({'hostname':'*.inria.fr'},['hostname','node_id']): print n
+{'hostname': u'vnode01.inria.fr', 'node_id': 1}
+{'hostname': u'vnode02.inria.fr', 'node_id': 2}
+
+### setting 'deployment' for these two nodes
+>>> SetNodeDeployment('vnode01.inria.fr','alpha')
+>>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
+{'groupname': u'alphanodes', 'node_ids': [1]}
+{'groupname': u'betanodes', 'node_ids': []}
+>>> SetNodeDeployment('vnode02.inria.fr','beta')
+
+### checking contents again
+>>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
+{'groupname': u'alphanodes', 'node_ids': [1]}
+{'groupname': u'betanodes', 'node_ids': [2]}
+</programlisting>
+</para>  
+
+    </section>
+
+    <section id="plcsh">
       <title>PlanetLab shell</title>
 
       <para>A command-line program called <command>plcsh</command>
@@ -240,6 +524,48 @@ nodes = GetNodes([121], ['node_id', 'hostname'])
 nodes = plc.GetNodes([121], ['node_id', 'hostname'])
       </programlisting>
     </section>
+
+  <section id='standalone'>
+    <title>Using regular python</title>
+
+    <para>It is also possible to write simple regular-python scripts,
+    as illustrated in the example below. The only difference with the
+    examples above is that all API calls need to be passed a first
+    argument for authentication. This example would write in a file
+    the name of all the hosts attached to a given slice.</para>
+
+<programlisting>
+#!/usr/bin/env python
+
+import xmlrpclib
+
+plc_host='www.planet-lab.eu'
+
+slice_name='inria_heartbeat'
+
+auth = { 'AuthMethod' : 'password',
+         'Username' : 'thierry.parmentelat@inria.fr',
+         'AuthString' : 'xxxxxx',
+}
+
+api_url="https://%s:443/PLCAPI/"%plc_host
+
+plc_api = xmlrpclib.ServerProxy(api_url,allow_none=True)
+
+# the slice's node ids
+node_ids = plc_api.GetSlices(auth,slice_name,['node_ids'])[0]['node_ids']
+
+# get hostname for these nodes
+slice_nodes = plc_api.GetNodes(auth,node_ids,['hostname'])
+
+# store in a file
+f=open('mynodes.txt','w')
+for node in slice_nodes:
+    print >>f,node['hostname']
+f.close()
+</programlisting>
+  </section>
+
   </chapter>
 
   <chapter id="Methods">