Revert "Remove vendored libjuju"
[osm/N2VC.git] / modules / libjuju / juju / unit.py
1 import logging
2
3 import pyrfc3339
4
5 from . import model
6 from .client import client
7
8 log = logging.getLogger(__name__)
9
10
11 class Unit(model.ModelEntity):
12 @property
13 def agent_status(self):
14 """Returns the current agent status string.
15
16 """
17 return self.safe_data['agent-status']['current']
18
19 @property
20 def agent_status_since(self):
21 """Get the time when the `agent_status` was last updated.
22
23 """
24 return pyrfc3339.parse(self.safe_data['agent-status']['since'])
25
26 @property
27 def agent_status_message(self):
28 """Get the agent status message.
29
30 """
31 return self.safe_data['agent-status']['message']
32
33 @property
34 def workload_status(self):
35 """Returns the current workload status string.
36
37 """
38 return self.safe_data['workload-status']['current']
39
40 @property
41 def workload_status_since(self):
42 """Get the time when the `workload_status` was last updated.
43
44 """
45 return pyrfc3339.parse(self.safe_data['workload-status']['since'])
46
47 @property
48 def workload_status_message(self):
49 """Get the workload status message.
50
51 """
52 return self.safe_data['workload-status']['message']
53
54 @property
55 def machine(self):
56 """Get the machine object for this unit.
57
58 """
59 machine_id = self.safe_data['machine-id']
60 if machine_id:
61 return self.model.machines.get(machine_id, None)
62 else:
63 return None
64
65 @property
66 def public_address(self):
67 """ Get the public address.
68
69 """
70 return self.safe_data['public-address'] or None
71
72 @property
73 def tag(self):
74 return 'unit-%s' % self.name.replace('/', '-')
75
76 def add_storage(self, name, constraints=None):
77 """Add unit storage dynamically.
78
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'
82
83 """
84 raise NotImplementedError()
85
86 def collect_metrics(self):
87 """Collect metrics on this unit.
88
89 """
90 raise NotImplementedError()
91
92 async def destroy(self):
93 """Destroy this unit.
94
95 """
96 app_facade = client.ApplicationFacade.from_connection(self.connection)
97
98 log.debug(
99 'Destroying %s', self.name)
100
101 return await app_facade.DestroyUnits([self.name])
102 remove = destroy
103
104 def get_resources(self, details=False):
105 """Return resources for this unit.
106
107 :param bool details: Include detailed info about resources used by each
108 unit
109
110 """
111 raise NotImplementedError()
112
113 def resolved(self, retry=False):
114 """Mark unit errors resolved.
115
116 :param bool retry: Re-execute failed hooks
117
118 """
119 raise NotImplementedError()
120
121 async def run(self, command, timeout=None):
122 """Run command on this unit.
123
124 :param str command: The command to run
125 :param int timeout: Time, in seconds, to wait before command is
126 considered failed
127 :returns: A :class:`juju.action.Action` instance.
128
129 """
130 action = client.ActionFacade.from_connection(self.connection)
131
132 log.debug(
133 'Running `%s` on %s', command, self.name)
134
135 if timeout:
136 # Convert seconds to nanoseconds
137 timeout = int(timeout * 1000000000)
138
139 res = await action.Run(
140 [],
141 command,
142 [],
143 timeout,
144 [self.name],
145 )
146 return await self.model.wait_for_action(res.results[0].action.tag)
147
148 async def run_action(self, action_name, **params):
149 """Run an action on this unit.
150
151 :param str action_name: Name of action to run
152 :param **params: Action parameters
153 :returns: A :class:`juju.action.Action` instance.
154
155 Note that this only enqueues the action. You will need to call
156 ``action.wait()`` on the resulting `Action` instance if you wish
157 to block until the action is complete.
158
159 """
160 action_facade = client.ActionFacade.from_connection(self.connection)
161
162 log.debug('Starting action `%s` on %s', action_name, self.name)
163
164 res = await action_facade.Enqueue([client.Action(
165 name=action_name,
166 parameters=params,
167 receiver=self.tag,
168 )])
169 action = res.results[0].action
170 error = res.results[0].error
171 if error and error.code == 'not found':
172 raise ValueError('Action `%s` not found on %s' % (action_name,
173 self.name))
174 elif error:
175 raise Exception('Unknown action error: %s' % error.serialize())
176 action_id = action.tag[len('action-'):]
177 log.debug('Action started as %s', action_id)
178 # we mustn't use wait_for_action because that blocks until the
179 # action is complete, rather than just being in the model
180 return await self.model._wait_for_new('action', action_id)
181
182 async def scp_to(self, source, destination, user='ubuntu', proxy=False,
183 scp_opts=''):
184 """Transfer files to this unit.
185
186 :param str source: Local path of file(s) to transfer
187 :param str destination: Remote destination of transferred files
188 :param str user: Remote username
189 :param bool proxy: Proxy through the Juju API server
190 :param scp_opts: Additional options to the `scp` command
191 :type scp_opts: str or list
192 """
193 await self.machine.scp_to(source, destination, user=user, proxy=proxy,
194 scp_opts=scp_opts)
195
196 async def scp_from(self, source, destination, user='ubuntu', proxy=False,
197 scp_opts=''):
198 """Transfer files from this unit.
199
200 :param str source: Remote path of file(s) to transfer
201 :param str destination: Local destination of transferred files
202 :param str user: Remote username
203 :param bool proxy: Proxy through the Juju API server
204 :param scp_opts: Additional options to the `scp` command
205 :type scp_opts: str or list
206 """
207 await self.machine.scp_from(source, destination, user=user,
208 proxy=proxy, scp_opts=scp_opts)
209
210 def set_meter_status(self):
211 """Set the meter status on this unit.
212
213 """
214 raise NotImplementedError()
215
216 def ssh(
217 self, command, user=None, proxy=False, ssh_opts=None):
218 """Execute a command over SSH on this unit.
219
220 :param str command: Command to execute
221 :param str user: Remote username
222 :param bool proxy: Proxy through the Juju API server
223 :param str ssh_opts: Additional options to the `ssh` command
224
225 """
226 raise NotImplementedError()
227
228 def status_history(self, num=20, utc=False):
229 """Get status history for this unit.
230
231 :param int num: Size of history backlog
232 :param bool utc: Display time as UTC in RFC3339 format
233
234 """
235 raise NotImplementedError()
236
237 async def is_leader_from_status(self):
238 """
239 Check to see if this unit is the leader. Returns True if so, and
240 False if it is not, or if leadership does not make sense
241 (e.g., there is no leader in this application.)
242
243 This method is a kluge that calls FullStatus in the
244 ClientFacade to get its information. Once
245 https://bugs.launchpad.net/juju/+bug/1643691 is resolved, we
246 should add a simple .is_leader property, and deprecate this
247 method.
248
249 """
250 app = self.name.split("/")[0]
251
252 c = client.ClientFacade.from_connection(self.connection)
253
254 status = await c.FullStatus(None)
255
256 # FullStatus may be more up to date than our model, and the
257 # unit may have gone away, or we may be doing something silly,
258 # like trying to fetch leadership for a subordinate, which
259 # will not be filed where we expect in the model. In those
260 # cases, we may simply return False, as a nonexistent or
261 # subordinate unit is not a leader.
262 if not status.applications.get(app):
263 return False
264
265 if not status.applications[app].get('units'):
266 return False
267
268 if not status.applications[app]['units'].get(self.name):
269 return False
270
271 return status.applications[app]['units'][self.name].get('leader',
272 False)
273
274 async def get_metrics(self):
275 """Get metrics for the unit.
276
277 :return: Dictionary of metrics for this unit.
278
279 """
280 metrics = await self.model.get_metrics(self.tag)
281 return metrics[self.name]