<?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 '&' 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:
+ <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, '&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>
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">