File: tutorial.rst

package info (click to toggle)
python-pyxs 0.4.2~git20190115.97f14313-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 328 kB
  • sloc: python: 1,196; makefile: 93
file content (147 lines) | stat: -rw-r--r-- 5,079 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
.. _tutorial:

Tutorial
========


Basics
------

Using :mod:`pyxs` is easy! The only class you need to import is
:class:`~pyxs.client.Client`. It provides a simple straightforward API
to XenStore content with a bit of Python's syntactic sugar here and
there.

Generally, if you just need to fetch or update some XenStore items you
can do::

   >>> from pyxs import Client
   >>> with Client() as c:
   ...     c[b"/local/domain/0/name"] = b"Ziggy"
   ...     c[b"/local/domain/0/name"]
   b'Ziggy'

Using :class:`~pyxs.client.Client` without the ``with`` statement is
possible, albeit, not recommended:

  >>> c = Client()
  >>> c.connect()
  >>> c[b"/local/domain/0/name"] = b"It works!"
  >>> c.close()

The reason for preferring a context manager is simple: you don't have
to DIY. The context manager will make sure that a started transaction
was either rolled back or committed and close the underlying XenStore
connection afterwards.

Connections
-----------

:mod:`pyxs` supports two ways of communicating with XenStore:

* over a Unix socket with :class:`~pyxs.connection.UnixSocketConnection`;
* over XenBus_ with :class:`~pyxs.connection.XenBusConnection`.

Connection type is determined from the arguments passed to
:class:`~pyxs.client.Client` constructor. For example, the
following code creates a :class:`~pyxs.client.Client` instance,
operating over a Unix socket::

    >>> Client(unix_socket_path="/var/run/xenstored/socket_ro")
    Client(UnixSocketConnection('/var/run/xenstored/socket_ro'))
    >>> Client()
    Client(UnixSocketConnection('/var/run/xenstored/socket'))

Use ``xen_bus_path`` argument to initialize a :class:`~pyxs.client.Client` with
:class:`~pyxs.connection.XenBusConnection`::

    >>> Client(xen_bus_path="/dev/xen/xenbus")
    Client(XenBusConnection('/dev/xen/xenbus'))

.. _XenBus: http://wiki.xensource.com/xenwiki/XenBus

Transactions
------------

Transactions allow you to operate on an isolated copy of XenStore tree
and merge your changes back atomically on commit. Keep in mind, however,
that changes made within a transaction become available to other XenStore
clients only if and when committed.  Here's an example::

    >>> with Client() as c:
    ...     c.transaction()
    ...     c[b"/foo/bar"] = b"baz"
    ...     c.commit()  # !
    ...     print(c[b"/foo/bar"])
    b'baz'

The line with an exclamation mark is a bit careless, because it
ignores the fact that committing a transaction might fail. A more
robust way to commit a transaction is by using a loop::

    >>> with Client() as c:
    ...     success = False
    ...     while not success:
    ...         c.transaction()
    ...         c[b"/foo/bar"] = b"baz"
    ...         success = c.commit()

You can also abort the current transaction by calling
:meth:`~pyxs.client.Client.rollback`.

Events
------

When a new path is created or an existing path is modified, XenStore
fires an event, notifying all watching clients that a change has been
made.  :mod:`pyxs` implements watching via the :class:`Monitor`
class. To watch a path create a monitor
:meth:`~pyxs.client.Client.monitor` and call
:meth:`~pyxs.client.Monitor.watch` with a path you want to watch and a
unique token. Right after that the monitor will start to accumulate
incoming events.  You can iterate over them via
:meth:`~pyxs.client.Monitor.wait`::

    >>> with Client() as c:
    ...    m = c.monitor()
    ...    m.watch(b"/foo/bar", b"a unique token")
    ...    next(m.wait())
    Event(b"/foo/bar", b"a unique token")

XenStore has a notion of *special* paths, which start with ``@`` and
are reserved for special occasions:

================  ================================================
Path              Description
----------------  ------------------------------------------------
@introduceDomain  Fired when a **new** domain is introduced to
                  XenStore -- you can also introduce domains
                  yourself with a
                  :meth:`~pyxs.client.Client.introduce_domain`
                  call, but in most of the cases, ``xenstored``
                  will do that for you.
@releaseDomain    Fired when XenStore is no longer communicating
                  with a domain, see
                  :meth:`~pyxs.client.Client.release_domain`.
================  ================================================

Events for both special and ordinary paths are simple two element
tuples, where the first element is always `event target` -- a path
which triggered the event and second is a token passed to
:meth:`~pyxs.client.Monitor.watch`. A rather unfortunate consequence
of this is that you can't get `domid` of the domain, which triggered
@introduceDomain or @releaseDomain from the received event.


Compatibility API
-----------------

:mod:`pyxs` also provides a compatibility interface, which mimics that
of ``xen.lowlevel.xs`` --- so you don't have to change
anything in the code to switch to :mod:`pyxs`::

   >>> from pyxs import xs
   >>> handle = xs()
   >>> handle.read("0", b"/local/domain/0/name")
   b'Domain-0'
   >>> handle.close()