How do I define a relationship?
So, in order to define a relationship between two charms you must first, as you have alluded to, define the relation in each charms
metadata.yaml file. Since you've defined a server/client role I'm going to stick to that in my examples below with
foo-client charms. Since it's likely the server providing the majority of the data to the client their metadata.yaml files would look as such:
description: Something more than this
description: Something more than this
Juju has two primary relation types. Provides and Requires. In this case the server charm is providing "foo" as an interface. The client charm requires the "foo" interface to operate. This provides/requires lets juju know which charms can talk to which other charms.
The interface is an arbitrary name, in this case foo, but could be anything. There's a large list of already defined interfaces, such as: mysql, http, mongodb, etc. If your service provides one of these existing interfaces you'd want to consider implementing it. If not feel free to create a new one.
How do I get/send data?
Once you've defined your metadata, you'll need to create a few new hooks the hook names are defined in the linked documentation, but since you're just sending the address information we'll keep with a simple bash example of the implementation of each hook.
So, we have two charms,
foo-server provides a "server" relation with the foo interface.
foo-client requires a "backend" relation with the foo interface. Relation hooks are named based on the relation-name (not the interface name). These could both be called server, but to illustrate that juju matches on interface and not relation, I've made the
foo-client relation name "backend".
relation-set hostname=`unit-get private-address`
This is a very basic example, where we're creating a relation key called
hostname and setting the value, using
unit-get command, to the private-address of the unit the charm is deployed to. This address will vary from provider to provider, but it will always be reachable within a juju environment. You can set multiple keys by adding a space between keys, for example:
relation-set hostname=`unit-get private-address` public-address=`unit-get public-address`
This will send two keys,
public-address to whatever service it's connected to.
Notice the difference in file name, this is invoking the
relation-changed hook instead of
relation-joined. Presumably the server is just giving the details of where it lives, so the client charm needs to know where that address is. By putting this in the relation-changed hook every time data on the relation is updated the hook is called again.
if [ -z "$server_address" ]; then
juju-log "No data sent yet"
# If you've gotten this far, you have a $server_address, configure as you see fit
Now, there's a little more involved in this hook. Taking it line by line, the first three are just standard stuff. It's a bash charm and
set -eux is there to make sure the hook behaves as it should. The next line uses
relation-get which will read relation data from the connection. Now, everything in a juju environment is orchestrated asynchronously. So you're never 100% certain you'll have data when you call
relation-get. This is where the
if block helps resolve that. If there's nothing in "$server_address", ie we didn't get a return value, the hook will simple exit. However, it's exiting with a zero status so it won't crop up as an error in juju.
I know this seems counter intuitive, we technically have a problem because we don't have data. Yes, but, it's more along the lines of "We don't have data, yet". By exiting zero, once the corresponding service actually sets the value, it'll trigger the
relation-changed hook again and we'll be able to read the value. This is considered an example of an idempotency guard which are crucial as you write hooks.