Tutorial¶
Basics¶
Using pyxs
is easy! The only class you need to import is
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 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¶
pyxs
supports two ways of communicating with XenStore:
- over a Unix socket with
UnixSocketConnection
; - over XenBus with
XenBusConnection
.
Connection type is determined from the arguments passed to
Client
constructor. For example, the
following code creates a 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 Client
with
XenBusConnection
:
>>> Client(xen_bus_path="/dev/xen/xenbus")
Client(XenBusConnection('/dev/xen/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
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. pyxs
implements watching via the Monitor
class. To watch a path create a monitor
monitor()
and call
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
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
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
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
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¶
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 pyxs
:
>>> from pyxs import xs
>>> handle = xs()
>>> handle.read("0", b"/local/domain/0/name")
b'Domain-0'
>>> handle.close()