Merge "Fix bug #502"
[osm/N2VC.git] / modules / libjuju / docs / narrative / model.rst
1 Models
2 ======
3 A Juju controller provides websocket endpoints for each of its
4 models. In order to do anything useful with a model, the juju lib must
5 connect to one of these endpoints. There are several ways to do this.
6
7 For api docs, see py:class:`juju.model.Model`.
8
9
10 Connecting to the Current Model
11 -------------------------------
12 Connect to the currently active Juju model (the one returned by
13 `juju switch`). This only works if you have the Juju CLI client installed.
14
15 .. code:: python
16
17   model = Model()
18   await model.connect()
19
20
21 Connecting to a Named Model
22 ---------------------------
23 Connect to a model by name, using the same format as that returned from the
24 `juju switch` command. The accepted format is '[controller:][user/]model'.
25 This only works if you have the Juju CLI client installed.
26
27 .. code:: python
28
29   model = Model()
30   await model.connect('juju-2.0.1:admin/libjuju')
31
32
33 Connecting with Authentication
34 ------------------------------
35 You can control what user you are connecting with by specifying either a
36 username/password pair, or a macaroon or bakery client that can provide
37 a macaroon.
38
39
40 .. code:: python
41
42   model = Model()
43   await model.connect(username='admin',
44                       password='f53f08cfc32a2e257fe5393271d89d62')
45
46   # or with a macaroon
47   await model.connect(macaroons=[
48       {
49           "Name": "macaroon-218d87053ad19626bcd5a0eef0bc9ba8bd4fbd80a968f52a5fd430b2aa8660df",
50           "Value": "W3siY2F2ZWF0cyI6 ... jBkZiJ9XQ==",
51           "Domain": "10.130.48.27",
52           "Path": "/auth",
53           "Secure": false,
54           "HostOnly": true,
55           "Expires": "2018-03-07T22:07:23Z",
56       },
57   ])
58
59   # or with a bakery client
60   from macaroonbakery.httpbakery import Client
61   from http.cookiejar import FileCookieJar
62
63   bakery_client=Client()
64   bakery_client.cookies = FileCookieJar('cookies.txt')
65   model = Model()
66   await model.connect(bakery_client=bakery_client)
67   
68
69
70 Connecting with an Explicit Endpoint
71 ------------------------------------
72 The most flexible, but also most verbose, way to connect is using the API
73 endpoint url, model UUID, and credentials directly. This method does NOT
74 require the Juju CLI client to be installed.
75
76 .. code:: python
77
78   from juju.model import Model
79
80   model = Model()
81   await model.connect(
82       endpoint='10.0.4.171:17070',
83       uuid='e8399ac7-078c-4817-8e5e-32316d55b083',
84       username='admin',
85       password='f53f08cfc32a2e257fe5393271d89d62',
86       cacert=None,  # Left out for brevity, but if you have a cert string you
87                     # should pass it in. You can get the cert from the output
88                     # of The `juju show-controller` command.
89   )
90
91
92 Creating and Destroying a Model
93 -------------------------------
94 Example of creating a new model and then destroying it. See
95 py:method:`juju.controller.Controller.add_model` and
96 py:method:`juju.controller.Controller.destroy_model` for more info.
97
98 .. code:: python
99
100   from juju.controller import Controller
101
102   controller = Controller()
103   await controller.connect_current()
104
105   # Create our new model
106   model = await controller.add_model(
107       'mymodel',  # name of your new model
108   )
109
110   # Do stuff with our model...
111
112   # Destroy the model
113   await model.disconnect()
114   await controller.destroy_model(model.info.uuid)
115   model = None
116
117
118 Adding Machines and Containers
119 ------------------------------
120 To add a machine or container, connect to a model and then call its
121 py:method:`~juju.model.Model.add_machine` method. A
122 py:class:`~juju.machine.Machine` instance is returned. The machine id
123 can be used to deploy a charm to a specific machine or container.
124
125 .. code:: python
126
127   from juju.model import Model
128
129   MB = 1
130   GB = 1024
131
132
133   model = Model()
134   await model.connect_current()
135
136   # add a new default machine
137   machine1 = await model.add_machine()
138
139   # add a machine with constraints, disks, and series specified
140   machine2 = await model.add_machine(
141       constraints={
142           'mem': 256 * MB,
143       },
144       disks=[{
145           'pool': 'rootfs',
146           'size': 10 * GB,
147           'count': 1,
148       }],
149       series='xenial',
150   )
151
152   # add a lxd container to machine2
153   machine3 = await model.add_machine(
154       'lxd:{}'.format(machine2.id))
155
156   # deploy charm to the lxd container
157   application = await model.deploy(
158       'ubuntu-10',
159       application_name='ubuntu',
160       series='xenial',
161       channel='stable',
162       to=machine3.id
163   )
164
165   # remove application
166   await application.remove()
167
168   # destroy machines - note that machine3 must be destroyed before machine2
169   # since it's a container on machine2
170   await machine3.destroy(force=True)
171   await machine2.destroy(force=True)
172   await machine1.destroy(force=True)
173
174
175 Reacting to Changes in a Model
176 ------------------------------
177 To watch for and respond to changes in a model, register an observer with the
178 model. The easiest way to do this is by creating a
179 py:class:`juju.model.ModelObserver` subclass.
180
181 .. code:: python
182
183   from juju.model import Model, ModelObserver
184
185   class MyModelObserver(ModelObserver):
186       async def on_change(self, delta, old, new, model):
187           # The raw change data (dict) from the websocket.
188           print(delta.data)
189
190           # The entity type (str) affected by this change.
191           # One of ('action', 'application', 'annotation', 'machine',
192           # 'unit', 'relation')
193           print(delta.entity)
194
195           # The type (str) of change.
196           # One of ('add', 'change', 'remove')
197           print(delta.type)
198
199           # The 'old' and 'new' parameters are juju.model.ModelEntity
200           # instances which represent an entity in the model both before
201           # this change was applied (old) and after (new).
202
203           # If an entity is being added to the model, the 'old' param
204           # will be None.
205           if delta.type == 'add':
206               assert(old is None)
207
208           # If an entity is being removed from the model, the 'new' param
209           # will be None.
210           if delta.type == 'remove':
211               assert(new is None)
212
213           # The 'old' and 'new' parameters, when not None, will be instances
214           # of a juju.model.ModelEntity subclass. The type of the subclass
215           # depends on the value of 'delta.entity', for example:
216           #
217           # delta.entity     type
218           # ------------     ----
219           # 'action'      -> juju.action.Action
220           # 'application' -> juju.application.Application
221           # 'annotation'  -> juju.annotation.Annotation
222           # 'machine'     -> juju.machine.Machine
223           # 'unit'        -> juju.unit.Unit
224           # 'relation'    -> juju.relation.Relation
225
226           # Finally, the 'model' parameter is a reference to the
227           # juju.model.Model instance to which this observer is attached.
228           print(id(model))
229
230
231   model = Model()
232   await model.connect_current()
233
234   model.add_observer(MyModelObserver())
235
236
237 Every change in the model will result in a call to the `on_change()`
238 method of your observer(s).
239
240 To target your code more precisely, define method names that correspond
241 to the entity and type of change that you wish to handle.
242
243 .. code:: python
244
245   from juju.model import Model, ModelObserver
246
247   class MyModelObserver(ModelObserver):
248       async def on_application_change(self, delta, old, new, model):
249           # Both 'old' and 'new' params will be instances of
250           # juju.application.Application
251           pass
252
253       async def on_unit_remove(self, delta, old, new, model):
254           # Since a unit is being removed, the 'new' param will always
255           # be None in this handler. The 'old' param will be an instance
256           # of juju.unit.Unit - the state of the unit before it was removed.
257           pass
258
259       async def on_machine_add(self, delta, old, new, model):
260           # Since a machine is being added, the 'old' param will always be
261           # None in this handler. The 'new' param will be an instance of
262           # juju.machine.Machine.
263           pass
264
265       async def on_change(self, delta, old, new, model):
266           # The catch-all handler - will be called whenever a more
267           # specific handler method is not defined.
268
269
270 Any py:class:`juju.model.ModelEntity` object can be observed directly by
271 registering callbacks on the object itself.
272
273 .. code:: python
274
275   import logging
276
277   async def on_app_change(delta, old, new, model):
278       logging.debug('App changed: %r', new)
279
280   async def on_app_remove(delta, old, new, model):
281       logging.debug('App removed: %r', old)
282
283   ubuntu_app = await model.deploy(
284       'ubuntu',
285       application_name='ubuntu',
286       series='trusty',
287       channel='stable',
288   )
289   ubuntu_app.on_change(on_app_change)
290   ubuntu_app.on_remove(on_app_remove)