74f7e844143c9420f76f69f77506239455c0de69
[osm/N2VC.git] / 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   model = Model()
146   await model.connect_current()
147
148   # add a new default machine
149   machine1 = await model.add_machine()
150
151   # add a machine with constraints, disks, and series specified
152   machine2 = await model.add_machine(
153       constraints={
154           'mem': 256 * MB,
155       },
156       disks=[{
157           'pool': 'rootfs',
158           'size': 10 * GB,
159           'count': 1,
160       }],
161       series='xenial',
162   )
163
164   # add a lxd container to machine2
165   machine3 = await model.add_machine(
166       'lxd:{}'.format(machine2.id))
167
168   # deploy charm to the lxd container
169   application = await model.deploy(
170       'ubuntu-10',
171       application_name='ubuntu',
172       series='xenial',
173       channel='stable',
174       to=machine3.id
175   )
176
177
178 Reacting to Changes in a Model
179 ------------------------------
180 To watch for and respond to changes in a model, register an observer with the
181 model. The easiest way to do this is by creating a
182 :class:`juju.model.ModelObserver` subclass.
183
184 .. code:: python
185
186   from juju.model import Model, ModelObserver
187
188   class MyModelObserver(ModelObserver):
189       async def on_change(self, delta, old, new, model):
190           # The raw change data (dict) from the websocket.
191           print(delta.data)
192
193           # The entity type (str) affected by this change.
194           # One of ('action', 'application', 'annotation', 'machine',
195           # 'unit', 'relation')
196           print(delta.entity)
197
198           # The type (str) of change.
199           # One of ('add', 'change', 'remove')
200           print(delta.type)
201
202           # The 'old' and 'new' parameters are juju.model.ModelEntity
203           # instances which represent an entity in the model both before
204           # this change was applied (old) and after (new).
205
206           # If an entity is being added to the model, the 'old' param
207           # will be None.
208           if delta.type == 'add':
209               assert(old is None)
210
211           # If an entity is being removed from the model, the 'new' param
212           # will be None.
213           if delta.type == 'remove':
214               assert(new is None)
215
216           # The 'old' and 'new' parameters, when not None, will be instances
217           # of a juju.model.ModelEntity subclass. The type of the subclass
218           # depends on the value of 'delta.entity', for example:
219           #
220           # delta.entity     type
221           # ------------     ----
222           # 'action'      -> juju.action.Action
223           # 'application' -> juju.application.Application
224           # 'annotation'  -> juju.annotation.Annotation
225           # 'machine'     -> juju.machine.Machine
226           # 'unit'        -> juju.unit.Unit
227           # 'relation'    -> juju.relation.Relation
228
229           # Finally, the 'model' parameter is a reference to the
230           # juju.model.Model instance to which this observer is attached.
231           print(id(model))
232
233
234   model = Model()
235   await model.connect_current()
236
237   model.add_observer(MyModelObserver())
238
239
240 Every change in the model will result in a call to the `on_change()`
241 method of your observer(s).
242
243 To target your code more precisely, define method names that correspond
244 to the entity and type of change that you wish to handle.
245
246 .. code:: python
247
248   from juju.model import Model, ModelObserver
249
250   class MyModelObserver(ModelObserver):
251       async def on_application_change(self, delta, old, new, model):
252           # Both 'old' and 'new' params will be instances of
253           # juju.application.Application
254           pass
255
256       async def on_unit_remove(self, delta, old, new, model):
257           # Since a unit is being removed, the 'new' param will always
258           # be None in this handler. The 'old' param will be an instance
259           # of juju.unit.Unit - the state of the unit before it was removed.
260           pass
261
262       async def on_machine_add(self, delta, old, new, model):
263           # Since a machine is being added, the 'old' param will always be
264           # None in this handler. The 'new' param will be an instance of
265           # juju.machine.Machine.
266           pass
267
268       async def on_change(self, delta, old, new, model):
269           # The catch-all handler - will be called whenever a more
270           # specific handler method is not defined.
271
272
273 Any :class:`juju.model.ModelEntity` object can be observed directly by
274 registering callbacks on the object itself.
275
276 .. code:: python
277
278   import logging
279
280   async def on_app_change(delta, old, new, model):
281       logging.debug('App changed: %r', new)
282
283   async def on_app_remove(delta, old, new, model):
284       logging.debug('App removed: %r', old)
285
286   ubuntu_app = await model.deploy(
287       'ubuntu',
288       application_name='ubuntu',
289       series='trusty',
290       channel='stable',
291   )
292   ubuntu_app.on_change(on_app_change)
293   ubuntu_app.on_remove(on_app_remove)