X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FN2VC.git;a=blobdiff_plain;f=modules%2Flibjuju%2Fdocs%2Fnarrative%2Fmodel.rst;fp=modules%2Flibjuju%2Fdocs%2Fnarrative%2Fmodel.rst;h=57dbc810f07aab33b339f418b05d6b2a57ea4b80;hp=0000000000000000000000000000000000000000;hb=68858c1915122c2dbc8999a5cd3229694abf5f3a;hpb=032a71b2a6692b8b4e30f629a1f906d246f06736 diff --git a/modules/libjuju/docs/narrative/model.rst b/modules/libjuju/docs/narrative/model.rst new file mode 100644 index 0000000..57dbc81 --- /dev/null +++ b/modules/libjuju/docs/narrative/model.rst @@ -0,0 +1,306 @@ +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)