Revert "Remove vendored libjuju"
[osm/N2VC.git] / modules / libjuju / docs / narrative / model.rst
diff --git a/modules/libjuju/docs/narrative/model.rst b/modules/libjuju/docs/narrative/model.rst
new file mode 100644 (file)
index 0000000..42633a1
--- /dev/null
@@ -0,0 +1,290 @@
+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 py: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
+
+  model = Model()
+  await model.connect()
+
+
+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
+
+  model = Model()
+  await model.connect('juju-2.0.1:admin/libjuju')
+
+
+Connecting with Authentication
+------------------------------
+You can control what user you are connecting with by specifying either a
+username/password pair, or a macaroon or bakery client that can provide
+a macaroon.
+
+
+.. code:: python
+
+  model = Model()
+  await model.connect(username='admin',
+                      password='f53f08cfc32a2e257fe5393271d89d62')
+
+  # or with a macaroon
+  await model.connect(macaroons=[
+      {
+          "Name": "macaroon-218d87053ad19626bcd5a0eef0bc9ba8bd4fbd80a968f52a5fd430b2aa8660df",
+          "Value": "W3siY2F2ZWF0cyI6 ... jBkZiJ9XQ==",
+          "Domain": "10.130.48.27",
+          "Path": "/auth",
+          "Secure": false,
+          "HostOnly": true,
+          "Expires": "2018-03-07T22:07:23Z",
+      },
+  ])
+
+  # or with a bakery client
+  from macaroonbakery.httpbakery import Client
+  from http.cookiejar import FileCookieJar
+
+  bakery_client=Client()
+  bakery_client.cookies = FileCookieJar('cookies.txt')
+  model = Model()
+  await model.connect(bakery_client=bakery_client)
+  
+
+
+Connecting with an Explicit Endpoint
+------------------------------------
+The most flexible, but also most verbose, way to connect is using the API
+endpoint url, model UUID, and credentials directly. This method does NOT
+require the Juju CLI client to be installed.
+
+.. code:: python
+
+  from juju.model import Model
+
+  model = Model()
+  await model.connect(
+      endpoint='10.0.4.171:17070',
+      uuid='e8399ac7-078c-4817-8e5e-32316d55b083',
+      username='admin',
+      password='f53f08cfc32a2e257fe5393271d89d62',
+      cacert=None,  # Left out for brevity, but if you have a cert string you
+                    # should pass it in. You can get the cert from the output
+                    # of The `juju show-controller` command.
+  )
+
+
+Creating and Destroying a Model
+-------------------------------
+Example of creating a new model and then destroying it. See
+py:method:`juju.controller.Controller.add_model` and
+py:method:`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
+py:method:`~juju.model.Model.add_machine` method. A
+py: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
+py: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 py: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)