Skip to content

Worker Developer Guide

Alba Hita Catala edited this page Oct 17, 2023 · 4 revisions

Yggdrasil uses D-Bus to facilitate communication between workers and yggd. It defines the interfaces over which yggd and its workers communicate:

  • com.redhat.Yggdrasil1.Dispatcher1 is the interface implemented by yggd and used by workers to communicate with yggd.
  • com.redhat.Yggdrasil1.Worker1 is the interface implemented by workers and used by yggd to communicate with workers.

A worker is any program that:

  • Connects to the D-Bus broker (either the system bus or a session bus)
  • Exports an object implementing the com.redhat.Yggdrasil1.Worker1 interface
  • Claims the well-known name com.redhat.Yggdrasil1.Worker1.NAME, where NAME is the identification string used to route messages received by yggd to the worker

Programming Languages

  • yggdrasil Workers can be written in any programming language, so long as the language can communicate using the D-Bus protocol. Many modern programming languages have libraries that implement the D-Bus protocol, making the decision about which programming language to use a matter of mostly personal preference. However, yggdrasil offers two libraries to support worker development: a Go package and a GObject C library (yggdrasil and libygg respectively). Both of these libraries handle all of the D-Bus specific "boilerplate" code, enabling the worker author to spend their time developing their application code instead. The GObject C library provides GObject Introspection files, allowing programming languages such as Python and JavaScript to use GObject Introspection to call functions in the C library.

Writing a "Hello" Worker

The following section documents how to develop and run a worker in Python, assuming yggdrasil is installed and running on the system. You are free to organize your code into any structure you prefer, so long as the outcome conforms to the 3 expectations above. This example worker will print any strings it receives to stdout, prefixing the string with "hello"; a classic "hello world" application.

Prerequisites

Initialize a worker with Python

A worker object is initialized by calling the Ygg.Worker initializer:

worker = Ygg.Worker(directive="hello", remote_content=False, features=None)

Define a function that will be called each time the worker receives data from yggd over D-Bus and pass a handle to the worker.

def handle_rx(worker, addr, id, response_to, meta_data, data):
    print("Hello {}".format(data)

worker.set_rx_func(handle_rx)

Start the worker; this will connect to the D-Bus broker, export an object implementing the com.redhat.Yggdrasil1.Worker1 interface and claim the name com.redhat.Yggdrasil1.Worker1.hello.

worker.connect()

Finally, start the main run loop to begin receiving messages on the D-Bus broker.

loop = GLib.MainLoop()
loop.run()

Initialize a worker with Go

Workers implemented in Go language can use the yggdrasil library.

Define a function that will be called each time the worker receives data from yggd over D-Bus and pass a handle to the worker.

func handleRx(
	w *worker.Worker,
	addr string,
	rcvId string,
	responseTo string,
	metadata map[string]string,
	data []byte,
) error {
	fmt.Printf("Hello %v", data)
}

The cancellation handler is also defined with and rx function.


Note: At this moment it is only implemented in yggdrasil

func cancelRx(w *worker.Worker, addr string, id string, cancelID string) error {
    fmt.Printf("cancelling message with id %v", cancelID)
}

Initialize the worker with the NewWorker function.

w, err := worker.NewWorker(
directive,
remoteContent,
features,
cancelEcho,
handleRx,
)

Finally, set a channel to receive exit syscalls, and connect the worker to a D-Bus session. This will export an object implementing the com.redhat.Yggdrasil1.Worker1 interface and claim the name com.redhat.Yggdrasil1.Worker1.hello. It is ready to start receiving message through D-Bus worker.

quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)

if err := w.Connect(quit); err != nil {
    log.Fatalf("error: cannot connect: %v", err)
}

D-Bus Security Policy

If your worker is likely to be run on the system bus, you must install a policy defining how messages can be sent. Install the following into a file in the directory /usr/share/dbus-1/system.d. Conventionally, the files are named after the destination name, so in this case, the file would be named com.redhat.Yggdrasil1.Worker1.hello.conf.

<busconfig>
<policy user="root">
 <!-- Only root can send messages to the Worker1.hello destination. -->
    <allow send_destination="com.redhat.Yggdrasil1.Worker1.hello" send_interface="com.redhat.Yggdrasil1.Worker1"/>
    <allow send_destination="com.redhat.Yggdrasil1.Worker1.hello" send_interface="org.freedesktop.DBus.Properties"/>
    <allow send_destination="com.redhat.Yggdrasil1.Worker1.hello" send_interface="org.freedesktop.DBus.Introspectable"/>
</policy>
</busconfig>

D-Bus Service Activation

A worker may optionally install a D-Bus service file to enable bus activation; when the D-Bus broker receives a message for the address specified by the Name= directive, the worker will be started before the message is delivered.

[D-BUS Service]
Name=com.redhat.Yggdrasil1.Worker1.hello
Exec=/usr/libexec/hello-worker

Should your worker require more advanced start up functionality, it is possible to specify a value for the SystemdService= directive. With such a directive in the service activation file, the D-Bus broker will ask systemd to start the named service. Within that system service file, you can define any resource limitations or environment variables needed for your worker. For example:

[Unit]
Description=yggdrasil hello worker

[Service]
Type=dbus
BusName=com.redhat.Yggdrasil1.Worker1.hello
ExecStart=/usr/libexec/hello-worker

Note: There is no [Install] section; this is because the service is D-Bus activatable.

See the systemd service examples for detail.

Additional Resources