| Models |
| ====== |
| A Juju controller provides websocket endpoints for each of its |
| models. In order to do anything useful with a model, the juju lib must |
| connect to one of these endpoints. There are several ways to do this. |
| |
| For api docs, see :class:`juju.model.Model`. |
| |
| |
| Connecting to the Current Model |
| ------------------------------- |
| Connect to the currently active Juju model (the one returned by |
| `juju switch`). This only works if you have the Juju CLI client installed. |
| |
| .. code:: python |
| |
| from juju.model import Model |
| |
| model = Model() |
| await model.connect_current() |
| |
| |
| Connecting to a Named Model |
| --------------------------- |
| Connect to a model by name, using the same format as that returned from the |
| `juju switch` command. The accepted format is '[controller:][user/]model'. |
| This only works if you have the Juju CLI client installed. |
| |
| .. code:: python |
| |
| # $ juju switch |
| # juju-2.0.1:admin/libjuju |
| |
| from juju.model import Model |
| |
| model = Model() |
| await model.connect_model('juju-2.0.1:admin/libjuju') |
| |
| |
| Connecting with Username/Password Authentication |
| ------------------------------------------------ |
| The most flexible, but also most verbose, way to connect is using the API |
| endpoint url and credentials directly. This method does NOT require the Juju |
| CLI client to be installed. |
| |
| .. code:: python |
| |
| from juju.model import Model |
| |
| model = Model() |
| |
| controller_endpoint = '10.0.4.171:17070' |
| model_uuid = 'e8399ac7-078c-4817-8e5e-32316d55b083' |
| username = 'admin' |
| password = 'f53f08cfc32a2e257fe5393271d89d62' |
| |
| # Left out for brevity, but if you have a cert string you should pass it in. |
| # You can copy the cert from the output of The `juju show-controller` |
| # command. |
| cacert = None |
| |
| await model.connect( |
| controller_endpoint, |
| model_uuid, |
| username, |
| password, |
| cacert, |
| ) |
| |
| |
| Connecting with Macaroon Authentication |
| --------------------------------------- |
| To connect to a shared model, or a model an a shared controller, you'll need |
| to use macaroon authentication. The simplest example is shown below, and uses |
| already-discharged macaroons from the local filesystem. This will work if you |
| have the Juju CLI installed. |
| |
| .. note:: |
| |
| The library does not yet contain support for fetching and discharging |
| macaroons. Until it does, if you want to use macaroon auth, you'll need |
| to supply already-discharged macaroons yourself. |
| |
| .. code:: python |
| |
| from juju.client.connection import get_macaroons() |
| from juju.model import Model |
| |
| model = Model() |
| |
| controller_endpoint = '10.0.4.171:17070' |
| model_uuid = 'e8399ac7-078c-4817-8e5e-32316d55b083' |
| username = None |
| password = None |
| cacert = None |
| macaroons = get_macaroons() |
| |
| await model.connect( |
| controller_endpoint, |
| model_uuid, |
| username, |
| password, |
| cacert, |
| macaroons, |
| ) |
| |
| |
| Creating and Destroying a Model |
| ------------------------------- |
| Example of creating a new model and then destroying it. See |
| :meth:`juju.controller.Controller.add_model` and |
| :meth:`juju.controller.Controller.destroy_model` for more info. |
| |
| .. code:: python |
| |
| from juju.controller import Controller |
| |
| controller = Controller() |
| await controller.connect_current() |
| |
| # Create our new model |
| model = await controller.add_model( |
| 'mymodel', # name of your new model |
| ) |
| |
| # Do stuff with our model... |
| |
| # Destroy the model |
| await model.disconnect() |
| await controller.destroy_model(model.info.uuid) |
| model = None |
| |
| |
| Adding Machines and Containers |
| ------------------------------ |
| To add a machine or container, connect to a model and then call its |
| :meth:`~juju.model.Model.add_machine` method. A |
| :class:`~juju.machine.Machine` instance is returned. The machine id |
| can be used to deploy a charm to a specific machine or container. |
| |
| .. code:: python |
| |
| from juju.model import Model |
| |
| MB = 1 |
| GB = 1024 |
| |
| |
| model = Model() |
| await model.connect_current() |
| |
| # add a new default machine |
| machine1 = await model.add_machine() |
| |
| # add a machine with constraints, disks, and series specified |
| machine2 = await model.add_machine( |
| constraints={ |
| 'mem': 256 * MB, |
| }, |
| disks=[{ |
| 'pool': 'rootfs', |
| 'size': 10 * GB, |
| 'count': 1, |
| }], |
| series='xenial', |
| ) |
| |
| # add a lxd container to machine2 |
| machine3 = await model.add_machine( |
| 'lxd:{}'.format(machine2.id)) |
| |
| # deploy charm to the lxd container |
| application = await model.deploy( |
| 'ubuntu-10', |
| application_name='ubuntu', |
| series='xenial', |
| channel='stable', |
| to=machine3.id |
| ) |
| |
| # remove application |
| await application.remove() |
| |
| # destroy machines - note that machine3 must be destroyed before machine2 |
| # since it's a container on machine2 |
| await machine3.destroy(force=True) |
| await machine2.destroy(force=True) |
| await machine1.destroy(force=True) |
| |
| |
| Reacting to Changes in a Model |
| ------------------------------ |
| To watch for and respond to changes in a model, register an observer with the |
| model. The easiest way to do this is by creating a |
| :class:`juju.model.ModelObserver` subclass. |
| |
| .. code:: python |
| |
| from juju.model import Model, ModelObserver |
| |
| class MyModelObserver(ModelObserver): |
| async def on_change(self, delta, old, new, model): |
| # The raw change data (dict) from the websocket. |
| print(delta.data) |
| |
| # The entity type (str) affected by this change. |
| # One of ('action', 'application', 'annotation', 'machine', |
| # 'unit', 'relation') |
| print(delta.entity) |
| |
| # The type (str) of change. |
| # One of ('add', 'change', 'remove') |
| print(delta.type) |
| |
| # The 'old' and 'new' parameters are juju.model.ModelEntity |
| # instances which represent an entity in the model both before |
| # this change was applied (old) and after (new). |
| |
| # If an entity is being added to the model, the 'old' param |
| # will be None. |
| if delta.type == 'add': |
| assert(old is None) |
| |
| # If an entity is being removed from the model, the 'new' param |
| # will be None. |
| if delta.type == 'remove': |
| assert(new is None) |
| |
| # The 'old' and 'new' parameters, when not None, will be instances |
| # of a juju.model.ModelEntity subclass. The type of the subclass |
| # depends on the value of 'delta.entity', for example: |
| # |
| # delta.entity type |
| # ------------ ---- |
| # 'action' -> juju.action.Action |
| # 'application' -> juju.application.Application |
| # 'annotation' -> juju.annotation.Annotation |
| # 'machine' -> juju.machine.Machine |
| # 'unit' -> juju.unit.Unit |
| # 'relation' -> juju.relation.Relation |
| |
| # Finally, the 'model' parameter is a reference to the |
| # juju.model.Model instance to which this observer is attached. |
| print(id(model)) |
| |
| |
| model = Model() |
| await model.connect_current() |
| |
| model.add_observer(MyModelObserver()) |
| |
| |
| Every change in the model will result in a call to the `on_change()` |
| method of your observer(s). |
| |
| To target your code more precisely, define method names that correspond |
| to the entity and type of change that you wish to handle. |
| |
| .. code:: python |
| |
| from juju.model import Model, ModelObserver |
| |
| class MyModelObserver(ModelObserver): |
| async def on_application_change(self, delta, old, new, model): |
| # Both 'old' and 'new' params will be instances of |
| # juju.application.Application |
| pass |
| |
| async def on_unit_remove(self, delta, old, new, model): |
| # Since a unit is being removed, the 'new' param will always |
| # be None in this handler. The 'old' param will be an instance |
| # of juju.unit.Unit - the state of the unit before it was removed. |
| pass |
| |
| async def on_machine_add(self, delta, old, new, model): |
| # Since a machine is being added, the 'old' param will always be |
| # None in this handler. The 'new' param will be an instance of |
| # juju.machine.Machine. |
| pass |
| |
| async def on_change(self, delta, old, new, model): |
| # The catch-all handler - will be called whenever a more |
| # specific handler method is not defined. |
| |
| |
| Any :class:`juju.model.ModelEntity` object can be observed directly by |
| registering callbacks on the object itself. |
| |
| .. code:: python |
| |
| import logging |
| |
| async def on_app_change(delta, old, new, model): |
| logging.debug('App changed: %r', new) |
| |
| async def on_app_remove(delta, old, new, model): |
| logging.debug('App removed: %r', old) |
| |
| ubuntu_app = await model.deploy( |
| 'ubuntu', |
| application_name='ubuntu', |
| series='trusty', |
| channel='stable', |
| ) |
| ubuntu_app.on_change(on_app_change) |
| ubuntu_app.on_remove(on_app_remove) |