New N2VC interface + updated libjuju
[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 :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   from juju.model import Model
18
19   model = Model()
20   await model.connect_current()
21
22
23 Connecting to a Named Model
24 ---------------------------
25 Connect to a model by name, using the same format as that returned from the
26 `juju switch` command. The accepted format is '[controller:][user/]model'.
27 This only works if you have the Juju CLI client installed.
28
29 .. code:: python
30
31   # $ juju switch
32   # juju-2.0.1:admin/libjuju
33
34   from juju.model import Model
35
36   model = Model()
37   await model.connect_model('juju-2.0.1:admin/libjuju')
38
39
40 Connecting with Username/Password Authentication
41 ------------------------------------------------
42 The most flexible, but also most verbose, way to connect is using the API
43 endpoint url and credentials directly. This method does NOT require the Juju
44 CLI client to be installed.
45
46 .. code:: python
47
48   from juju.model import Model
49
50   model = Model()
51
52   controller_endpoint = '10.0.4.171:17070'
53   model_uuid = 'e8399ac7-078c-4817-8e5e-32316d55b083'
54   username = 'admin'
55   password = 'f53f08cfc32a2e257fe5393271d89d62'
56
57   # Left out for brevity, but if you have a cert string you should pass it in.
58   # You can copy the cert from the output of The `juju show-controller`
59   # command.
60   cacert = None
61
62   await model.connect(
63       controller_endpoint,
64       model_uuid,
65       username,
66       password,
67       cacert,
68   )
69
70
71 Connecting with Macaroon Authentication
72 ---------------------------------------
73 To connect to a shared model, or a model an a shared controller, you'll need
74 to use macaroon authentication. The simplest example is shown below, and uses
75 already-discharged macaroons from the local filesystem. This will work if you
76 have the Juju CLI installed.
77
78 .. note::
79
80   The library does not yet contain support for fetching and discharging
81   macaroons. Until it does, if you want to use macaroon auth, you'll need
82   to supply already-discharged macaroons yourself.
83
84 .. code:: python
85
86   from juju.client.connection import get_macaroons()
87   from juju.model import Model
88
89   model = Model()
90
91   controller_endpoint = '10.0.4.171:17070'
92   model_uuid = 'e8399ac7-078c-4817-8e5e-32316d55b083'
93   username = None
94   password = None
95   cacert = None
96   macaroons = get_macaroons()
97
98   await model.connect(
99       controller_endpoint,
100       model_uuid,
101       username,
102       password,
103       cacert,
104       macaroons,
105   )
106
107
108 Creating and Destroying a Model
109 -------------------------------
110 Example of creating a new model and then destroying it. See
111 :meth:`juju.controller.Controller.add_model` and
112 :meth:`juju.controller.Controller.destroy_model` for more info.
113
114 .. code:: python
115
116   from juju.controller import Controller
117
118   controller = Controller()
119   await controller.connect_current()
120
121   # Create our new model
122   model = await controller.add_model(
123       'mymodel',  # name of your new model
124   )
125
126   # Do stuff with our model...
127
128   # Destroy the model
129   await model.disconnect()
130   await controller.destroy_model(model.info.uuid)
131   model = None
132
133
134 Adding Machines and Containers
135 ------------------------------
136 To add a machine or container, connect to a model and then call its
137 :meth:`~juju.model.Model.add_machine` method. A
138 :class:`~juju.machine.Machine` instance is returned. The machine id
139 can be used to deploy a charm to a specific machine or container.
140
141 .. code:: python
142
143   from juju.model import Model
144
145   MB = 1
146   GB = 1024
147
148
149   model = Model()
150   await model.connect_current()
151
152   # add a new default machine
153   machine1 = await model.add_machine()
154
155   # add a machine with constraints, disks, and series specified
156   machine2 = await model.add_machine(
157       constraints={
158           'mem': 256 * MB,
159       },
160       disks=[{
161           'pool': 'rootfs',
162           'size': 10 * GB,
163           'count': 1,
164       }],
165       series='xenial',
166   )
167
168   # add a lxd container to machine2
169   machine3 = await model.add_machine(
170       'lxd:{}'.format(machine2.id))
171
172   # deploy charm to the lxd container
173   application = await model.deploy(
174       'ubuntu-10',
175       application_name='ubuntu',
176       series='xenial',
177       channel='stable',
178       to=machine3.id
179   )
180
181   # remove application
182   await application.remove()
183
184   # destroy machines - note that machine3 must be destroyed before machine2
185   # since it's a container on machine2
186   await machine3.destroy(force=True)
187   await machine2.destroy(force=True)
188   await machine1.destroy(force=True)
189
190
191 Reacting to Changes in a Model
192 ------------------------------
193 To watch for and respond to changes in a model, register an observer with the
194 model. The easiest way to do this is by creating a
195 :class:`juju.model.ModelObserver` subclass.
196
197 .. code:: python
198
199   from juju.model import Model, ModelObserver
200
201   class MyModelObserver(ModelObserver):
202       async def on_change(self, delta, old, new, model):
203           # The raw change data (dict) from the websocket.
204           print(delta.data)
205
206           # The entity type (str) affected by this change.
207           # One of ('action', 'application', 'annotation', 'machine',
208           # 'unit', 'relation')
209           print(delta.entity)
210
211           # The type (str) of change.
212           # One of ('add', 'change', 'remove')
213           print(delta.type)
214
215           # The 'old' and 'new' parameters are juju.model.ModelEntity
216           # instances which represent an entity in the model both before
217           # this change was applied (old) and after (new).
218
219           # If an entity is being added to the model, the 'old' param
220           # will be None.
221           if delta.type == 'add':
222               assert(old is None)
223
224           # If an entity is being removed from the model, the 'new' param
225           # will be None.
226           if delta.type == 'remove':
227               assert(new is None)
228
229           # The 'old' and 'new' parameters, when not None, will be instances
230           # of a juju.model.ModelEntity subclass. The type of the subclass
231           # depends on the value of 'delta.entity', for example:
232           #
233           # delta.entity     type
234           # ------------     ----
235           # 'action'      -> juju.action.Action
236           # 'application' -> juju.application.Application
237           # 'annotation'  -> juju.annotation.Annotation
238           # 'machine'     -> juju.machine.Machine
239           # 'unit'        -> juju.unit.Unit
240           # 'relation'    -> juju.relation.Relation
241
242           # Finally, the 'model' parameter is a reference to the
243           # juju.model.Model instance to which this observer is attached.
244           print(id(model))
245
246
247   model = Model()
248   await model.connect_current()
249
250   model.add_observer(MyModelObserver())
251
252
253 Every change in the model will result in a call to the `on_change()`
254 method of your observer(s).
255
256 To target your code more precisely, define method names that correspond
257 to the entity and type of change that you wish to handle.
258
259 .. code:: python
260
261   from juju.model import Model, ModelObserver
262
263   class MyModelObserver(ModelObserver):
264       async def on_application_change(self, delta, old, new, model):
265           # Both 'old' and 'new' params will be instances of
266           # juju.application.Application
267           pass
268
269       async def on_unit_remove(self, delta, old, new, model):
270           # Since a unit is being removed, the 'new' param will always
271           # be None in this handler. The 'old' param will be an instance
272           # of juju.unit.Unit - the state of the unit before it was removed.
273           pass
274
275       async def on_machine_add(self, delta, old, new, model):
276           # Since a machine is being added, the 'old' param will always be
277           # None in this handler. The 'new' param will be an instance of
278           # juju.machine.Machine.
279           pass
280
281       async def on_change(self, delta, old, new, model):
282           # The catch-all handler - will be called whenever a more
283           # specific handler method is not defined.
284
285
286 Any :class:`juju.model.ModelEntity` object can be observed directly by
287 registering callbacks on the object itself.
288
289 .. code:: python
290
291   import logging
292
293   async def on_app_change(delta, old, new, model):
294       logging.debug('App changed: %r', new)
295
296   async def on_app_remove(delta, old, new, model):
297       logging.debug('App removed: %r', old)
298
299   ubuntu_app = await model.deploy(
300       'ubuntu',
301       application_name='ubuntu',
302       series='trusty',
303       channel='stable',
304   )
305   ubuntu_app.on_change(on_app_change)
306   ubuntu_app.on_remove(on_app_remove)