blob: 57dbc810f07aab33b339f418b05d6b2a57ea4b80 [file] [log] [blame]
Adam Israeldcdf82b2017-08-15 15:26:43 -04001Models
2======
3A Juju controller provides websocket endpoints for each of its
4models. In order to do anything useful with a model, the juju lib must
5connect to one of these endpoints. There are several ways to do this.
6
7For api docs, see :class:`juju.model.Model`.
8
9
10Connecting to the Current Model
11-------------------------------
12Connect 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
23Connecting to a Named Model
24---------------------------
25Connect 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'.
27This 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
40Connecting with Username/Password Authentication
41------------------------------------------------
42The most flexible, but also most verbose, way to connect is using the API
43endpoint url and credentials directly. This method does NOT require the Juju
44CLI 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
71Connecting with Macaroon Authentication
72---------------------------------------
73To connect to a shared model, or a model an a shared controller, you'll need
74to use macaroon authentication. The simplest example is shown below, and uses
75already-discharged macaroons from the local filesystem. This will work if you
76have 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
108Creating and Destroying a Model
109-------------------------------
110Example 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
134Adding Machines and Containers
135------------------------------
136To 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
139can 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
191Reacting to Changes in a Model
192------------------------------
193To watch for and respond to changes in a model, register an observer with the
194model. 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
253Every change in the model will result in a call to the `on_change()`
254method of your observer(s).
255
256To target your code more precisely, define method names that correspond
257to 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
286Any :class:`juju.model.ModelEntity` object can be observed directly by
287registering 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)