64fbe4438692b8959117d4ee6f675efa697234b7
5 import macaroonbakery
.httpbakery
as httpbakery
6 from juju
.client
.connection
import Connection
7 from juju
.client
.jujudata
import FileJujuData
8 from juju
.errors
import JujuConnectionError
, JujuError
10 log
= logging
.getLogger('connector')
13 class NoConnectionException(Exception):
14 '''Raised by Connector when the connection method is called
15 and there is no current connection.'''
20 '''This class abstracts out a reconnectable client that can connect
21 to controllers and models found in the Juju data files.
30 '''Initialize a connector that will use the given parameters
31 by default when making a new connection'''
32 self
.max_frame_size
= max_frame_size
33 self
.loop
= loop
or asyncio
.get_event_loop()
34 self
.bakery_client
= bakery_client
35 self
._connection
= None
36 self
.controller_name
= None
37 self
.model_name
= None
38 self
.jujudata
= jujudata
or FileJujuData()
40 def is_connected(self
):
41 '''Report whether there is a currently connected controller or not'''
42 return self
._connection
is not None
45 '''Return the current connection; raises an exception if there
46 is no current connection.'''
47 if not self
.is_connected():
48 raise NoConnectionException('not connected')
49 return self
._connection
51 async def connect(self
, **kwargs
):
52 """Connect to an arbitrary Juju model.
54 kwargs are passed through to Connection.connect()
56 kwargs
.setdefault('loop', self
.loop
)
57 kwargs
.setdefault('max_frame_size', self
.max_frame_size
)
58 kwargs
.setdefault('bakery_client', self
.bakery_client
)
59 self
._connection
= await Connection
.connect(**kwargs
)
61 async def disconnect(self
):
62 """Shut down the watcher task and close websockets.
65 log
.debug('Closing model connection')
66 await self
._connection
.close()
67 self
._connection
= None
69 async def connect_controller(self
, controller_name
=None):
70 """Connect to a controller by name. If the name is empty, it
71 connect to the current controller.
73 if not controller_name
:
74 controller_name
= self
.jujudata
.current_controller()
75 if not controller_name
:
76 raise JujuConnectionError('No current controller')
78 controller
= self
.jujudata
.controllers()[controller_name
]
79 # TODO change Connection so we can pass all the endpoints
80 # instead of just the first.
81 endpoint
= controller
['api-endpoints'][0]
82 accounts
= self
.jujudata
.accounts().get(controller_name
, {})
87 username
=accounts
.get('user'),
88 password
=accounts
.get('password'),
89 cacert
=controller
.get('ca-cert'),
90 bakery_client
=self
.bakery_client_for_controller(controller_name
),
92 self
.controller_name
= controller_name
94 async def connect_model(self
, model_name
=None):
95 """Connect to a model by name. If either controller or model
96 parts of the name are empty, the current controller and/or model
99 :param str model: <controller>:<model>
103 controller_name
, model_name
= self
.jujudata
.parse_model(model_name
)
104 controller
= self
.jujudata
.controllers().get(controller_name
)
105 except JujuError
as e
:
106 raise JujuConnectionError(e
.message
) from e
107 if controller
is None:
108 raise JujuConnectionError('Controller {} not found'.format(
110 # TODO change Connection so we can pass all the endpoints
111 # instead of just the first one.
112 endpoint
= controller
['api-endpoints'][0]
113 account
= self
.jujudata
.accounts().get(controller_name
, {})
114 models
= self
.jujudata
.models().get(controller_name
, {}).get('models',
116 if model_name
not in models
:
117 raise JujuConnectionError('Model not found: {}'.format(model_name
))
119 # TODO if there's no record for the required model name, connect
120 # to the controller to find out the model's uuid, then connect
121 # to that. This will let connect_model work with models that
122 # haven't necessarily synced with the local juju data,
123 # and also remove the need for base.CleanModel to
127 uuid
=models
[model_name
]['uuid'],
128 username
=account
.get('user'),
129 password
=account
.get('password'),
130 cacert
=controller
.get('ca-cert'),
131 bakery_client
=self
.bakery_client_for_controller(controller_name
),
133 self
.controller_name
= controller_name
134 self
.model_name
= controller_name
+ ':' + model_name
136 def bakery_client_for_controller(self
, controller_name
):
137 '''Make a copy of the bakery client with a the appropriate controller's
140 bakery_client
= self
.bakery_client
142 bakery_client
= copy
.copy(bakery_client
)
144 bakery_client
= httpbakery
.Client()
145 bakery_client
.cookies
= self
.jujudata
.cookies_for_controller(