6 from .client
import client
8 log
= logging
.getLogger(__name__
)
11 class Unit(model
.ModelEntity
):
13 def agent_status(self
):
14 """Returns the current agent status string.
17 return self
.safe_data
['agent-status']['current']
20 def agent_status_since(self
):
21 """Get the time when the `agent_status` was last updated.
24 return pyrfc3339
.parse(self
.safe_data
['agent-status']['since'])
27 def agent_status_message(self
):
28 """Get the agent status message.
31 return self
.safe_data
['agent-status']['message']
34 def workload_status(self
):
35 """Returns the current workload status string.
38 return self
.safe_data
['workload-status']['current']
41 def workload_status_since(self
):
42 """Get the time when the `workload_status` was last updated.
45 return pyrfc3339
.parse(self
.safe_data
['workload-status']['since'])
48 def workload_status_message(self
):
49 """Get the workload status message.
52 return self
.safe_data
['workload-status']['message']
56 """Get the machine object for this unit.
59 machine_id
= self
.safe_data
['machine-id']
61 return self
.model
.machines
.get(machine_id
, None)
66 def public_address(self
):
67 """ Get the public address.
70 return self
.safe_data
['public-address'] or None
74 return 'unit-%s' % self
.name
.replace('/', '-')
76 def add_storage(self
, name
, constraints
=None):
77 """Add unit storage dynamically.
79 :param str name: Storage name, as specified by the charm
80 :param str constraints: Comma-separated list of constraints in the
81 form 'POOL,COUNT,SIZE'
84 raise NotImplementedError()
86 def collect_metrics(self
):
87 """Collect metrics on this unit.
90 raise NotImplementedError()
92 async def destroy(self
):
96 app_facade
= client
.ApplicationFacade
.from_connection(self
.connection
)
99 'Destroying %s', self
.name
)
101 return await app_facade
.DestroyUnits([self
.name
])
104 def get_resources(self
, details
=False):
105 """Return resources for this unit.
107 :param bool details: Include detailed info about resources used by each
111 raise NotImplementedError()
113 def resolved(self
, retry
=False):
114 """Mark unit errors resolved.
116 :param bool retry: Re-execute failed hooks
119 raise NotImplementedError()
121 async def run(self
, command
, timeout
=None):
122 """Run command on this unit.
124 :param str command: The command to run
125 :param int timeout: Time, in seconds, to wait before command is considered failed
126 :returns: A :class:`juju.action.Action` instance.
129 action
= client
.ActionFacade
.from_connection(self
.connection
)
132 'Running `%s` on %s', command
, self
.name
)
135 # Convert seconds to nanoseconds
136 timeout
= int(timeout
* 1000000000)
138 res
= await action
.Run(
145 return await self
.model
.wait_for_action(res
.results
[0].action
.tag
)
147 async def run_action(self
, action_name
, **params
):
148 """Run an action on this unit.
150 :param str action_name: Name of action to run
151 :param \*\*params: Action parameters
152 :returns: A :class:`juju.action.Action` instance.
154 Note that this only enqueues the action. You will need to call
155 ``action.wait()`` on the resulting `Action` instance if you wish
156 to block until the action is complete.
159 action_facade
= client
.ActionFacade
.from_connection(self
.connection
)
161 log
.debug('Starting action `%s` on %s', action_name
, self
.name
)
163 res
= await action_facade
.Enqueue([client
.Action(
168 action
= res
.results
[0].action
169 error
= res
.results
[0].error
170 if error
and error
.code
== 'not found':
171 raise ValueError('Action `%s` not found on %s' % (action_name
,
174 raise Exception('Unknown action error: %s' % error
.serialize())
175 action_id
= action
.tag
[len('action-'):]
176 log
.debug('Action started as %s', action_id
)
177 # we mustn't use wait_for_action because that blocks until the
178 # action is complete, rather than just being in the model
179 return await self
.model
._wait
_for
_new
('action', action_id
)
181 async def scp_to(self
, source
, destination
, user
='ubuntu', proxy
=False,
183 """Transfer files to this unit.
185 :param str source: Local path of file(s) to transfer
186 :param str destination: Remote destination of transferred files
187 :param str user: Remote username
188 :param bool proxy: Proxy through the Juju API server
189 :param str scp_opts: Additional options to the `scp` command
191 await self
.machine
.scp_to(source
, destination
, user
=user
, proxy
=proxy
,
194 async def scp_from(self
, source
, destination
, user
='ubuntu', proxy
=False,
196 """Transfer files from this unit.
198 :param str source: Remote path of file(s) to transfer
199 :param str destination: Local destination of transferred files
200 :param str user: Remote username
201 :param bool proxy: Proxy through the Juju API server
202 :param str scp_opts: Additional options to the `scp` command
204 await self
.machine
.scp_from(source
, destination
, user
=user
,
205 proxy
=proxy
, scp_opts
=scp_opts
)
207 def set_meter_status(self
):
208 """Set the meter status on this unit.
211 raise NotImplementedError()
214 self
, command
, user
=None, proxy
=False, ssh_opts
=None):
215 """Execute a command over SSH on this unit.
217 :param str command: Command to execute
218 :param str user: Remote username
219 :param bool proxy: Proxy through the Juju API server
220 :param str ssh_opts: Additional options to the `ssh` command
223 raise NotImplementedError()
225 def status_history(self
, num
=20, utc
=False):
226 """Get status history for this unit.
228 :param int num: Size of history backlog
229 :param bool utc: Display time as UTC in RFC3339 format
232 raise NotImplementedError()
234 async def is_leader_from_status(self
):
236 Check to see if this unit is the leader. Returns True if so, and
237 False if it is not, or if leadership does not make sense
238 (e.g., there is no leader in this application.)
240 This method is a kluge that calls FullStatus in the
241 ClientFacade to get its information. Once
242 https://bugs.launchpad.net/juju/+bug/1643691 is resolved, we
243 should add a simple .is_leader property, and deprecate this
247 app
= self
.name
.split("/")[0]
249 c
= client
.ClientFacade
.from_connection(self
.connection
)
251 status
= await c
.FullStatus(None)
253 # FullStatus may be more up to date than our model, and the
254 # unit may have gone away, or we may be doing something silly,
255 # like trying to fetch leadership for a subordinate, which
256 # will not be filed where we expect in the model. In those
257 # cases, we may simply return False, as a nonexistent or
258 # subordinate unit is not a leader.
259 if not status
.applications
.get(app
):
262 if not status
.applications
[app
].get('units'):
265 if not status
.applications
[app
]['units'].get(self
.name
):
268 return status
.applications
[app
]['units'][self
.name
].get('leader',
271 async def get_metrics(self
):
272 """Get metrics for the unit.
274 :return: Dictionary of metrics for this unit.
277 metrics
= await self
.model
.get_metrics(self
.tag
)
278 return metrics
[self
.name
]