</section>
<section id="numeric">
- <title> Nueric comparisons </title>
+ <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>
</section>
+ <section id="sequence">
+ <title> Filtering on a sequence field </title>
+ <para> A field starting with '&' or '|' should refer to a sequence type;
+ the semantics is then that the object's value (expected to be a list)
+ should contain all (&) or any (|) value specified in the corresponding
+ filter value.
+ <programlisting> GetPersons ( { '|role_ids' : [ 20, 40 ] } ) </programlisting>
+ <programlisting> GetPersons ( { '|roles' : ['tech', 'pi'] } ) </programlisting>
+ <programlisting> GetPersons ( { '&roles' : ['admin', 'tech'] } ) </programlisting>
+ <programlisting> GetPersons ( { '&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:
</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, '&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 and Slices) are eligible for being dynamically
+ 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>
<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>
- 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
- (category, description) that is mostly informative. The
- convention is to use a category that depicts the type of
- objects that the tag type, like e.g. <emphasis>
- node/config </emphasis>
- </para>
+ <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>
+ 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> You would then be allowed to attach a value to, say,
- a Node, by calling <emphasis> AddNodeTag </emphasis>, and
- then as usual change this value with <emphasis>
+ <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>
greatly simplify tags manipulation as they take care of
<itemizedlist>
<listitem>
- <para> Lazily create <emphasis> TagTypes </emphasis> when
- needed,</para>
+ <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>
+
<para> <emphasis> Site-specific </emphasis> accessors can be
defined in <emphasis>
/usr/share/plc_api/PLC/Accessors/Accessors_site.py </emphasis>
- that will be preserved across updates of the PLCAPI rpm.
+ and will be preserved across updates of the
+ <emphasis>plcapi</emphasis> rpm.
</para>
+
<para>
- This mechanism does not currently support setting slice
+ The accessors mechanism does not currently support setting slice
tags that apply only on a given node or nodegroup.
</para>
</section>
</para></listitem>
<listitem><para>
- The current limitation about tags as opposed to native
- fields is that, for performance, tags won't get returned
+ 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'
# 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>
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')
-21
->>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
+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
+>>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print(ng)
{'groupname': u'alphanodes', 'node_ids': [1]}
{'groupname': u'betanodes', 'node_ids': []}
->>> SetNodeDeployment('vnode01.inria.fr','beta')
->>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
-{'groupname': u'alphanodes', 'node_ids': []}
-{'groupname': u'betanodes', 'node_ids': [1]}
+>>> 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>
the <function>PLC.Shell</function> module:</para>
<programlisting>
-#!/usr/bin/python
+#!/usr/bin/python3
import sys
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 python3
+
+import xmlrpc.client
+
+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 = xmlrpc.client.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
+with ('mynodes.txt','a') as f:
+ for node in slice_nodes:
+ f.write(node['hostname'] + "\n")
+f.close()
+</programlisting>
+ </section>
+
</chapter>
<chapter id="Methods">