79fb1a8e83fbe32c016e24e9a9e95c618c5f18a0
[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 limitations 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         <listitem><para>
376           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:
377 <programlisting>
378 # if 'hrn' is not part of the result, this does not work
379 >>> ns=GetNodes({'hrn':'ple.*'},['hostname'])
380 Database error b59e068c-589a-4ad5-9dd8-63cc38f2a2eb:
381 column "hrn" does not exist
382 LINE 1: ...M view_nodes WHERE deleted IS False AND (True AND hrn ILIKE ...
383 ... abridged ...
384 # this can be worked around by just returning 'hrn' as well
385 >>> ns=GetNodes({'hrn':'ple.*'},['hrn','hostname'])
386 </programlisting>
387         </para></listitem>
388
389       </itemizedlist>
390     </para>
391       </section>
392     </section>
393
394     <section id="nodegroups">
395     <title>Nodegroups</title>
396
397     <para> In earlier versions up to v4.2, <emphasis> NodeGroups
398     </emphasis> used to be defined extensively. So you would,
399     basically, create an empty nodegroup instance, and then use
400     <emphasis> AddNodeToNodeGroup </emphasis> or <emphasis>
401     DeleteNodeFromNodeGroup </emphasis> to manage the nodegroup's
402     contents. </para>
403
404     <para> The new model has been redefined as follows. You now define
405     a nodegroup as the set of nodes for which a given <emphasis> Tag
406     </emphasis> has a given value, which are defined once and for good
407     when creating the <emphasis> NodeGroup </emphasis> object. </para>
408
409     <para> So for instance for managing the set of nodes that are
410     running various levels of software code, PLC has defined two
411     <emphasis> NodeGroups </emphasis> named <literal> alpha </literal>
412     and <literal> beta </literal>. With the new model, we would now do
413     something like the following, using the built-in <literal>
414     deployment </literal> tag that is created for that purpose:
415 <programlisting>
416 ### creating node groups
417 >>> AddNodeGroup('alphanodes','deployment','alpha')
418 21
419 >>> AddNodeGroup('betanodes','deployment','beta')
420 22
421 ### checking contents (no node has 'deployment' set to either 'alpha' or 'beta' yet)
422 >>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
423 {'groupname': u'alphanodes', 'node_ids': []}
424 {'groupname': u'betanodes', 'node_ids': []}
425
426 ### displaying node ids 
427 >>> for n in GetNodes({'hostname':'*.inria.fr'},['hostname','node_id']): print n
428 {'hostname': u'vnode01.inria.fr', 'node_id': 1}
429 {'hostname': u'vnode02.inria.fr', 'node_id': 2}
430
431 ### setting 'deployment' for these two nodes
432 >>> SetNodeDeployment('vnode01.inria.fr','alpha')
433 >>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
434 {'groupname': u'alphanodes', 'node_ids': [1]}
435 {'groupname': u'betanodes', 'node_ids': []}
436 >>> SetNodeDeployment('vnode02.inria.fr','beta')
437
438 ### checking contents again
439 >>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
440 {'groupname': u'alphanodes', 'node_ids': [1]}
441 {'groupname': u'betanodes', 'node_ids': [2]}
442 </programlisting>
443 </para>  
444
445     </section>
446
447     <section id="plcsh">
448       <title>PlanetLab shell</title>
449
450       <para>A command-line program called <command>plcsh</command>
451       simplifies authentication structure handling, and is useful for
452       scripting. This program is distributed as a Linux RPM called
453       PLCAPI and requires Python &ge;2.4.</para>
454
455       <programlisting>
456 usage: plcsh [options]
457
458 options:
459   -f CONFIG, --config=CONFIG
460                         PLC configuration file
461   -h URL, --url=URL     API URL
462   -c CACERT, --cacert=CACERT
463                         API SSL certificate
464   -k INSECURE, --insecure=INSECURE
465                         Do not check SSL certificate
466   -m METHOD, --method=METHOD
467                         API authentication method
468   -s SESSION, --session=SESSION
469                         API session key
470   -u USER, --user=USER  API user name
471   -p PASSWORD, --password=PASSWORD
472                         API password
473   -r ROLE, --role=ROLE  API role
474   -x, --xmlrpc          Use XML-RPC interface
475   --help                show this help message and exit
476       </programlisting>
477
478       <para>Specify at least the API URL and your user name:</para>
479
480       <programlisting>
481 plcsh --url https://www.planet-lab.org/PLCAPI/ -u user@site.edu
482       </programlisting>
483
484       <para>You will be presented with a prompt. From here, you can
485       invoke API calls and omit the authentication structure, as it will
486       be filled in automatically.</para>
487
488       <programlisting>
489 user@site.edu connected using password authentication
490 Type "system.listMethods()" or "help(method)" for more information.
491 [user@site.edu]>>> AuthCheck()
492 1
493 [user@site.edu]>>> GetNodes([121], ['node_id', 'hostname'])
494 [{'node_id': 121, 'hostname': 'planetlab-1.cs.princeton.edu'}]
495       </programlisting>
496
497       <para>As this program is actually a Python interpreter, you may
498       create variables, execute for loops, import other packages, etc.,
499       directly on the command line as you would using the regular Python
500       shell.</para>
501
502       <para>To use <command>plcsh</command> programmatically, import
503       the <function>PLC.Shell</function> module:</para>
504
505       <programlisting>
506 #!/usr/bin/python
507
508 import sys
509
510 # Default location that the PLCAPI RPM installs the PLC class
511 sys.path.append('/usr/share/plc_api')
512
513 # Initialize shell environment. Shell() will define all PLCAPI methods
514 # in the specified namespace (specifying globals() will define them
515 # globally).
516 from PLC.Shell import Shell
517 plc = Shell(globals(),
518             url = "https://www.planet-lab.org/PLCAPI/",
519             user = "user@site.edu",
520             password = "password")
521
522 # Both are equivalent
523 nodes = GetNodes([121], ['node_id', 'hostname'])
524 nodes = plc.GetNodes([121], ['node_id', 'hostname'])
525       </programlisting>
526     </section>
527
528   <section id='standalone'>
529     <title>Using regular python</title>
530
531     <para>It is also possible to write simple regular-python scripts,
532     as illustrated in the example below. The only difference with the
533     examples above is that all API calls need to be passed a first
534     argument for authentication. This example would write in a file
535     the name of all the hosts attached to a given slice.</para>
536
537 <programlisting>
538 #!/usr/bin/env python
539
540 import xmlrpclib
541
542 plc_host='www.planet-lab.eu'
543
544 slice_name='inria_heartbeat'
545
546 auth = { 'AuthMethod' : 'password',
547          'Username' : 'thierry.parmentelat@inria.fr',
548          'AuthString' : 'xxxxxx',
549 }
550
551 api_url="https://%s:443/PLCAPI/"%plc_host
552
553 plc_api = xmlrpclib.ServerProxy(api_url,allow_none=True)
554
555 # the slice's node ids
556 node_ids = plc_api.GetSlices(auth,slice_name,['node_ids'])[0]['node_ids']
557
558 # get hostname for these nodes
559 slice_nodes = plc_api.GetNodes(auth,node_ids,['hostname'])
560
561 # store in a file
562 f=open('mynodes.txt','w')
563 for node in slice_nodes:
564     print >>f,node['hostname']
565 f.close()
566 </programlisting>
567   </section>
568
569   </chapter>
570
571   <chapter id="Methods">
572     <title>PlanetLab API Methods</title>
573     <para></para>
574
575     &Methods;
576   </chapter>
577
578 </book>
579
580 <!-- LocalWords:  PlanetLab API PLCAPI RPC HTTPS listMethods methodSignature
581 -->
582 <!-- LocalWords:  methodHelp multicall AuthMethod GetSession GnuPG Username GPG
583 -->
584 <!-- LocalWords:  AuthString AddPersonKey AddPeer UpdatePeer gpg
585 -->