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()
|