updated doc and comments
[plcapi.git] / doc / PLCAPI.xml.in
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- -*-xml-*- -->
3 <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
4 "@DOCBOOK-43@" [
5 <!ENTITY Methods SYSTEM "Methods.xml">
6 ]>
7
8 <book>
9   <bookinfo>
10     <title>PlanetLab Central API Documentation</title>
11   </bookinfo>
12
13   <chapter id="Introduction">
14     <title>Introduction</title>
15
16     <para>The PlanetLab Central API (PLCAPI) is the interface through
17     which the PlanetLab Central database should be accessed and
18     maintained. The API is used by the website, by nodes, by automated
19     scripts, and by users to access and update information about
20     users, nodes, sites, slices, and other entities maintained by the
21     database.</para>
22
23     <section id="Authentication">
24       <title>Authentication</title>
25
26       <para>The API should be accessed via XML-RPC over HTTPS. The API
27       supports the standard introspection calls <link
28       linkend="system.listMethods">system.listMethods</link>, <link
29       linkend="system.methodSignature">system.methodSignature</link>,
30       and <link linkend="system.methodHelp">system.methodHelp</link>,
31       and the standard batching call <link
32       linkend="system.multicall">system.multicall</link>. With the
33       exception of these calls, all PLCAPI calls take an
34       authentication structure as their first argument. All
35       authentication structures require the specification of
36       <parameter>AuthMethod</parameter>. If the documentation for a
37       call does not further specify the authentication structure, then
38       any of (but only) the following authentication structures may be
39       used:</para>
40
41       <itemizedlist>
42         <listitem>
43           <para>Session authentication. User sessions are typically
44           valid for 24 hours. Node sessions are valid until the next
45           reboot. Obtain a session key with <link
46           linkend="GetSession">GetSession</link> using another form of
47           authentication, such as password or GnuPG
48           authentication.</para>
49           <informaltable frame="none" rules="rows">
50             <tgroup cols="3">
51               <tbody>
52                 <row><entry>AuthMethod</entry><entry><literal>session</literal></entry></row>
53                 <row><entry>session</entry><entry>Session key</entry></row>
54               </tbody>
55             </tgroup>
56           </informaltable>
57         </listitem>
58         <listitem>
59           <para>Password authentication.</para>
60           <informaltable frame="none" rules="rows">
61             <tgroup cols="3">
62               <tbody>
63                 <row><entry>AuthMethod</entry><entry><literal>password</literal></entry></row>
64                 <row><entry>Username</entry><entry>Username, typically an e-mail address</entry></row>
65                 <row><entry>AuthString</entry><entry>Authentication string, typically a password</entry></row>
66               </tbody>
67             </tgroup>
68           </informaltable>
69         </listitem>
70         <listitem>
71           <para>GnuPG authentication. Users may upload a GPG public key
72           using <link linkend="AddPersonKey">AddPersonKey</link>. Peer
73           GPG keys should be added with <link
74           linkend="AddPeer">AddPeer</link> or <link
75           linkend="UpdatePeer">UpdatePeer</link>.
76           </para>
77           <informaltable frame="none" rules="rows">
78             <tgroup cols="3">
79               <tbody>
80                 <row><entry>AuthMethod</entry><entry><literal>gpg</literal></entry></row>
81                 <row><entry>name</entry><entry>Peer or user name</entry></row>
82                 <row><entry>signature</entry><entry>GnuPG signature of
83                 the <ulink
84                 url="http://www.w3.org/TR/xml-c14n">canonicalized</ulink>
85                 <ulink url="http://www.xmlrpc.com/spec">XML-RPC</ulink>
86                 representation of the rest of the arguments to the
87                 call.</entry></row>
88               </tbody>
89             </tgroup>
90           </informaltable>
91         </listitem>
92         <listitem>
93           <para>Anonymous authentication.</para>
94           <informaltable frame="none" rules="rows">
95             <tgroup cols="3">
96               <tbody>
97                 <row><entry>AuthMethod</entry><entry><literal>anonymous</literal></entry></row>
98               </tbody>
99             </tgroup>
100           </informaltable>
101         </listitem>
102       </itemizedlist>
103     </section>
104
105     <section id="Roles">
106       <title>Roles</title>
107
108       <para>Some functions may only be called by users with certain
109       roles (see <link linkend="GetRoles">GetRoles</link>), and others
110       may return different information to different callers depending
111       on the role(s) of the caller.</para>
112
113       <para>The <literal>node</literal> and
114       <literal>anonymous</literal> roles are pseudo-roles. A function
115       that allows the <literal>node</literal> role may be called by
116       automated scripts running on a node, such as the Boot and Node
117       Managers. A function that allows the
118       <literal>anonymous</literal> role may be called by anyone; an
119       API authentication structure must still be specified (see <xref
120       linkend="Authentication"/>).</para>
121     </section>
122
123     <section id="Filters">
124       <title>Filters</title>
125
126       <para>Most of the <function>Get</function> methods take a
127       filter argument. Filters may be arrays of integer (and sometimes
128       string) identifiers, or a struct representing a filter on the
129       attributes of the entities being queried. For example,
130
131 <programlisting>
132 >>> GetNodes([1,2,3])
133 >>> GetNodes({'node_id': [1,2,3]})
134 </programlisting>
135 </para>
136
137       <para>Would be equivalent queries. Attributes that are
138       themselves arrays (such as <literal>interface_ids</literal>
139       and <literal>slice_ids</literal> for nodes) cannot be used in
140       filters.</para>
141
142       <para> Filters support a few extra features illustrated in the following examples.</para>
143       
144       <section id="pattern-matching">
145         <title> Pattern Matching</title>
146         <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:
147           <programlisting>GetNodes ( { 'hostname' : '*.fr' } ) </programlisting>
148         </para>
149       </section>
150
151       <section id="negation">
152         <title> Negation </title>
153         <para> Fields starting with a <literal>~</literal> are negated, so non-local nodes can be fetched with:
154         <programlisting>GetNodes( { '~peer_id' : None } ) </programlisting>
155         </para>
156       </section>
157
158       <section id="numeric">
159         <title> Numeric comparisons </title>
160         <para> Strictly greater/smaller operations are achieved by prepending the field name like in:
161         <programlisting>GetEvents( { '>time' : 1178531418 } ) </programlisting>
162         </para>
163         <para> Greater/smaller or equal: 
164         <programlisting>GetEvents( { ']event_id' : 2305 } ) </programlisting>
165         </para>
166       </section>
167
168       <section id="sequence">
169         <title> Filtering on a sequence field </title>
170         <para> A field starting with '&amp;' or '|' should refer to a sequence type;
171       the semantics is then that the object's value (expected to be a list)
172       should contain all (&amp;) or any (|) value specified in the corresponding
173       filter value. 
174       <programlisting> GetPersons ( { '|role_ids' : [ 20, 40 ] } ) </programlisting>
175       <programlisting> GetPersons ( { '|roles' : ['tech', 'pi'] } ) </programlisting>
176       <programlisting> GetPersons ( { '&amp;roles' : ['admin', 'tech'] } ) </programlisting>
177       <programlisting> GetPersons ( { '&amp;roles' : 'tech' } ) </programlisting>
178         </para>
179       </section>
180
181       <section id="sort-clip">
182         <title> Sorting and Clipping </title> 
183         <para> The following 3 special fields can be used to extract only a subset of the results for pagination:
184           <programlisting> GetNodes( { '-SORT' : 'hostname' , '-OFFSET' : 30 , '-LIMIT' : 25 }</programlisting>
185         </para>
186       </section>
187     </section>
188
189     <section id="and-or">
190       <title> All criteria / Any criteria </title>
191       <para> The default in the vast majority of the code is to select
192       objects that match ALL the criteria specified in the struct. It
193       is possible to search for objects that match ANY of these by
194       adding the special '-OR' key (the value is then ignored), as in:
195       <programlisting> GetPersons ( { '-OR' : 'anything', 'site_id':2, '&amp;roles':['admin'] } ) </programlisting>
196       </para>
197     </section>
198
199     <section id="tags">
200       <title>Tags</title>
201
202       <para> The PLC API comes with a feature called
203       <emphasis>tags</emphasis>, that basically aims at supporting an
204       extensible data model. A few classes (as of this writing, Nodes,
205       Interfaces, Sites, Persons and Slices) are eligible for being dynamically
206       extended beyond the basic set of fields that are built into the
207       database schema.</para>
208
209       <para> Historically, this is a generalization of the concept of
210       <emphasis> SliceAttribute </emphasis>, and the more recent
211       concept of <emphasis> InterfaceSetting </emphasis>, that with
212       release 5.0 have been renamed into <emphasis> SliceTag
213       </emphasis> and <emphasis> InterfaceTag </emphasis>,
214       respectively. </para>
215
216       <section id="tags-low-level">
217         <title> Low level </title>
218         <para> The low level interface to tags relies on the following items:
219       <itemizedlist>
220         <listitem><para> 
221           A <emphasis> TagType </emphasis> object basically models a
222           new column that needs to be added to other objects. In much
223           the same way as nodes are named through a <emphasis>
224           hostname </emphasis>, tagtypes are named with a
225           <emphasis>tagname</emphasis>, plus additional information
226           (<emphasis>category</emphasis>,
227           <emphasis>description</emphasis>).  
228         </para> </listitem>
229
230         <listitem><para>
231           <emphasis>description</emphasis> is mostly informative, it
232             is used by the web interface to provide more details on
233             the meaning of that tag. 
234         </para> </listitem>
235
236         <listitem>
237          <para>
238           <emphasis>category</emphasis> is used in a variety of ways,
239           in the web interface again.  Over time this has become a
240           means to attach various information to a tag type, so it is
241           used as some sort of a poorman's tag tag system :).
242          </para>
243         </listitem>
244
245         <listitem>
246         <para>
247            The convention is to set in category a set of slash-separated
248            fields, like the following real examples demonstrate.
249 <programlisting> 
250 >>> tagnames=['arch','fcdistro','hrn','hmac','exempt_node_until']
251 >>> for tt in GetTagTypes(tagnames,['tagname','category']): 
252 >>> ... print "tagname=%-18s category=%s"%(tt['tagname'], tt['category'])
253 tagname=hrn                category=node/sfa
254 tagname=hmac               category=slice/auth   
255 tagname=exempt_node_until  category=node/myops
256 tagname=fcdistro           category=node/slice/config/ui/header=f/rank=w
257 tagname=arch               category=node/slice/config/ui/header=A/rank=x
258 </programlisting>
259          </para>
260         </listitem>
261
262         <listitem> <para> <emphasis>roles</emphasis> may also be
263         attached to a given tag_type (use AddRoleToTagType or
264         DeleteRoleFromTagType). This is an evolution over the former
265         system based on so-called 'min_role_id', and now any set of
266         roles may be mentioned. More importantly, each type (Node,
267         Person, ...) implements its own policy to let or not non-admin
268         callers change their tags. For example in the current
269         implementation, non-admin users can only change their own
270         person tags. See PLC/AuthorizeHelpers.py for that code.
271         </para> </listitem>
272
273         <listitem>
274           <para> The low-level method for managaing tags is then, once
275           the TagType is known to the system, to attach a value to,
276           say, a Node, by calling <emphasis> AddNodeTag </emphasis>,
277           and then as usual change this value with <emphasis>
278           UpdateNodeTag </emphasis>, or delete it with <emphasis>
279           DeleteNodeTag </emphasis>. </para>
280         </listitem>
281       </itemizedlist>
282     </para>
283       </section>
284
285       <section id="accessors">
286         <title> Accessors </title>
287       <para> A rather more convenient way to use tags is through
288       Accessors. This convenience is located in <emphasis>
289       PLC/Accessors </emphasis>, and allows you to easily define Get
290       or Set methods dedicated to a given tag. This is for instance
291       how the <emphasis> GetNodeArch </emphasis> and <emphasis>
292       SetNodeArch </emphasis> methods are implemented. These methods
293       greatly simplify tags manipulation as they take care of
294       <itemizedlist>
295         <listitem>
296           <para> Creating and enforcing <emphasis> TagTypes
297           </emphasis>; each time you restart your plc, the tag_types
298           mentioned in accessor definitions are created and checked
299           (in terms of the category, description and roles defined in
300           the various calls to define_accessors).</para>
301         </listitem>
302         <listitem>
303           <para> Create or update the, say, <emphasis> NodeTag
304           </emphasis> object, as needed.</para>
305         </listitem>
306         <listitem> <para> In addition, an accessor definition mentions
307         <emphasis> get_roles </emphasis> (defaults to all_roles), and
308         <emphasis>set_roles </emphasis>. These values are used as
309         follows. <emphasis> get_roles </emphasis> is attached to the
310         Get accessor, so callers that do not have this role cannot run
311         the Get accessor. <emphasis> set_roles </emphasis> is attached
312         to the Set accessor, as well as to the corresponding TagType,
313         which in turn is used for checking write access to the tag
314         type. </para>
315         </listitem>
316       </itemizedlist>
317     </para>
318
319       <para> <emphasis> Site-specific </emphasis> accessors can be
320       defined in <emphasis>
321       /usr/share/plc_api/PLC/Accessors/Accessors_site.py </emphasis>
322       and will be preserved across updates of the
323       <emphasis>plcapi</emphasis> rpm.
324       </para>
325
326       <para> 
327         The accessors mechanism does not currently support setting slice
328         tags that apply only on a given node or nodegroup. 
329       </para>
330       </section>
331
332       <section id="expose-in-api">
333         <title> Through regular Add/Get/Update methods </title>
334       <para> 
335         Finally, tags may also get manipulated through the
336         <emphasis>AddNode</emphasis>, <emphasis>GetNodes</emphasis>,
337         and <emphasis>UpdateNode</emphasis> methods:
338
339       <itemizedlist>
340         <listitem> <para> 
341           The <literal>define_accessors</literal> function in the
342           Accessors factory has an optional argument named <literal>
343           expose_in_api </literal>. When this is set, the
344           corresponding tag becomes visible from the Add/Get/Update
345           methods almost as if it was a native tag.
346         </para> </listitem>
347
348         <listitem><para>
349           So for instance the following code would be legal and do as expected:
350 <programlisting>
351 # create a x86_64 node
352 >>> AddNode({'hostname':'pl1.foo.com','arch':'x86_64'})
353 # get details for pl1.foo.com including tag 'arch' tag
354 >>> GetNodes(['pl1.foo.com'],['boot_state','node_type','arch'])
355 # set the 'deployment' tag
356 >>> UpdateNode('pl1.foo.com',{'deployment':'beta'})
357 # get all alpha and beta nodes
358 >>> GetNodes({'deployment':'*a'},['hostname','deployment'])
359 </programlisting>
360         </para></listitem>
361
362         <listitem><para> 
363           The current limitation about tags as opposed to native
364           fields is that, for performance, tags won't get returned
365           when using the implicit set of columns. So for instance:
366 <programlisting>
367 # get all details for 'pl1.foo.com' 
368 >>> node=GetNodes(['pl1.foo.com'])[0]
369 # this did not return the 'arch' tag
370 >>> 'arch' in node
371 False
372 </programlisting>
373         </para></listitem>
374
375       </itemizedlist>
376     </para>
377       </section>
378     </section>
379
380     <section id="nodegroups">
381     <title>Nodegroups</title>
382
383     <para> In earlier versions up to v4.2, <emphasis> NodeGroups
384     </emphasis> used to be defined extensively. So you would,
385     basically, create an empty nodegroup instance, and then use
386     <emphasis> AddNodeToNodeGroup </emphasis> or <emphasis>
387     DeleteNodeFromNodeGroup </emphasis> to manage the nodegroup's
388     contents. </para>
389
390     <para> The new model has been redefined as follows. You now define
391     a nodegroup as the set of nodes for which a given <emphasis> Tag
392     </emphasis> has a given value, which are defined once and for good
393     when creating the <emphasis> NodeGroup </emphasis> object. </para>
394
395     <para> So for instance for managing the set of nodes that are
396     running various levels of software code, PLC has defined two
397     <emphasis> NodeGroups </emphasis> named <literal> alpha </literal>
398     and <literal> beta </literal>. With the new model, we would now do
399     something like the following, using the built-in <literal>
400     deployment </literal> tag that is created for that purpose:
401 <programlisting>
402 ### creating node groups
403 >>> AddNodeGroup('alphanodes','deployment','alpha')
404 21
405 >>> AddNodeGroup('betanodes','deployment','beta')
406 22
407 ### checking contents (no node has 'deployment' set to either 'alpha' or 'beta' yet)
408 >>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
409 {'groupname': u'alphanodes', 'node_ids': []}
410 {'groupname': u'betanodes', 'node_ids': []}
411
412 ### displaying node ids 
413 >>> for n in GetNodes({'hostname':'*.inria.fr'},['hostname','node_id']): print n
414 {'hostname': u'vnode01.inria.fr', 'node_id': 1}
415 {'hostname': u'vnode02.inria.fr', 'node_id': 2}
416
417 ### setting 'deployment' for these two nodes
418 >>> SetNodeDeployment('vnode01.inria.fr','alpha')
419 >>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
420 {'groupname': u'alphanodes', 'node_ids': [1]}
421 {'groupname': u'betanodes', 'node_ids': []}
422 >>> SetNodeDeployment('vnode02.inria.fr','beta')
423
424 ### checking contents again
425 >>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
426 {'groupname': u'alphanodes', 'node_ids': [1]}
427 {'groupname': u'betanodes', 'node_ids': [2]}
428 </programlisting>
429 </para>  
430
431     </section>
432
433     <section id="plcsh">
434       <title>PlanetLab shell</title>
435
436       <para>A command-line program called <command>plcsh</command>
437       simplifies authentication structure handling, and is useful for
438       scripting. This program is distributed as a Linux RPM called
439       PLCAPI and requires Python &ge;2.4.</para>
440
441       <programlisting>
442 usage: plcsh [options]
443
444 options:
445   -f CONFIG, --config=CONFIG
446                         PLC configuration file
447   -h URL, --url=URL     API URL
448   -c CACERT, --cacert=CACERT
449                         API SSL certificate
450   -k INSECURE, --insecure=INSECURE
451                         Do not check SSL certificate
452   -m METHOD, --method=METHOD
453                         API authentication method
454   -s SESSION, --session=SESSION
455                         API session key
456   -u USER, --user=USER  API user name
457   -p PASSWORD, --password=PASSWORD
458                         API password
459   -r ROLE, --role=ROLE  API role
460   -x, --xmlrpc          Use XML-RPC interface
461   --help                show this help message and exit
462       </programlisting>
463
464       <para>Specify at least the API URL and your user name:</para>
465
466       <programlisting>
467 plcsh --url https://www.planet-lab.org/PLCAPI/ -u user@site.edu
468       </programlisting>
469
470       <para>You will be presented with a prompt. From here, you can
471       invoke API calls and omit the authentication structure, as it will
472       be filled in automatically.</para>
473
474       <programlisting>
475 user@site.edu connected using password authentication
476 Type "system.listMethods()" or "help(method)" for more information.
477 [user@site.edu]>>> AuthCheck()
478 1
479 [user@site.edu]>>> GetNodes([121], ['node_id', 'hostname'])
480 [{'node_id': 121, 'hostname': 'planetlab-1.cs.princeton.edu'}]
481       </programlisting>
482
483       <para>As this program is actually a Python interpreter, you may
484       create variables, execute for loops, import other packages, etc.,
485       directly on the command line as you would using the regular Python
486       shell.</para>
487
488       <para>To use <command>plcsh</command> programmatically, import
489       the <function>PLC.Shell</function> module:</para>
490
491       <programlisting>
492 #!/usr/bin/python
493
494 import sys
495
496 # Default location that the PLCAPI RPM installs the PLC class
497 sys.path.append('/usr/share/plc_api')
498
499 # Initialize shell environment. Shell() will define all PLCAPI methods
500 # in the specified namespace (specifying globals() will define them
501 # globally).
502 from PLC.Shell import Shell
503 plc = Shell(globals(),
504             url = "https://www.planet-lab.org/PLCAPI/",
505             user = "user@site.edu",
506             password = "password")
507
508 # Both are equivalent
509 nodes = GetNodes([121], ['node_id', 'hostname'])
510 nodes = plc.GetNodes([121], ['node_id', 'hostname'])
511       </programlisting>
512     </section>
513
514   <section id='standalone'>
515     <title>Using regular python</title>
516
517     <para>It is also possible to write simple regular-python scripts,
518     as illustrated in the example below. The only difference with the
519     examples above is that all API calls need to be passed a first
520     argument for authentication. This example would write in a file
521     the name of all the hosts attached to a given slice.</para>
522
523 <programlisting>
524 #!/usr/bin/env python
525
526 import xmlrpclib
527
528 plc_host='www.planet-lab.eu'
529
530 slice_name='inria_heartbeat'
531
532 auth = { 'AuthMethod' : 'password',
533          'Username' : 'thierry.parmentelat@inria.fr',
534          'AuthString' : 'xxxxxx',
535 }
536
537 api_url="https://%s:443/PLCAPI/"%plc_host
538
539 plc_api = xmlrpclib.ServerProxy(api_url,allow_none=True)
540
541 # the slice's node ids
542 node_ids = plc_api.GetSlices(auth,slice_name,['node_ids'])[0]['node_ids']
543
544 # get hostname for these nodes
545 slice_nodes = plc_api.GetNodes(auth,node_ids,['hostname'])
546
547 # store in a file
548 f=open('mynodes.txt','w')
549 for node in slice_nodes:
550     print >>f,node['hostname']
551 f.close()
552 </programlisting>
553   </section>
554
555   </chapter>
556
557   <chapter id="Methods">
558     <title>PlanetLab API Methods</title>
559     <para></para>
560
561     &Methods;
562   </chapter>
563
564 </book>
565
566 <!-- LocalWords:  PlanetLab API PLCAPI RPC HTTPS listMethods methodSignature
567 -->
568 <!-- LocalWords:  methodHelp multicall AuthMethod GetSession GnuPG Username GPG
569 -->
570 <!-- LocalWords:  AuthString AddPersonKey AddPeer UpdatePeer gpg
571 -->