Merge changes Iacd2f028,I43a6d573,Ibb6c93bb
[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 considered failed
126 :returns: A :class:`juju.action.Action` instance.
127
128 """
129 action = client.ActionFacade.from_connection(self.connection)
130
131 log.debug(
132 'Running `%s` on %s', command, self.name)
133
134 if timeout:
135 # Convert seconds to nanoseconds
136 timeout = int(timeout * 1000000000)
137
138 res = await action.Run(
139 [],
140 command,
141 [],
142 timeout,
143 [self.name],
144 )
145 return await self.model.wait_for_action(res.results[0].action.tag)
146
147 async def run_action(self, action_name, **params):
148 """Run an action on this unit.
149
150 :param str action_name: Name of action to run
151 :param \*\*params: Action parameters
152 :returns: A :class:`juju.action.Action` instance.
153
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.
157
158 """
159 action_facade = client.ActionFacade.from_connection(self.connection)
160
161 log.debug('Starting action `%s` on %s', action_name, self.name)
162
163 res = await action_facade.Enqueue([client.Action(
164 name=action_name,
165 parameters=params,
166 receiver=self.tag,
167 )])
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,
172 self.name))
173 elif error:
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)
180
181 async def scp_to(self, source, destination, user='ubuntu', proxy=False,
182 scp_opts=''):
183 """Transfer files to this unit.
184
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
190 """
191 await self.machine.scp_to(source, destination, user=user, proxy=proxy,
192 scp_opts=scp_opts)
193
194 async def scp_from(self, source, destination, user='ubuntu', proxy=False,
195 scp_opts=''):
196 """Transfer files from this unit.
197
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
203 """
204 await self.machine.scp_from(source, destination, user=user,
205 proxy=proxy, scp_opts=scp_opts)
206
207 def set_meter_status(self):
208 """Set the meter status on this unit.
209
210 """
211 raise NotImplementedError()
212
213 def ssh(
214 self, command, user=None, proxy=False, ssh_opts=None):
215 """Execute a command over SSH on this unit.
216
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
221
222 """
223 raise NotImplementedError()
224
225 def status_history(self, num=20, utc=False):
226 """Get status history for this unit.
227
228 :param int num: Size of history backlog
229 :param bool utc: Display time as UTC in RFC3339 format
230
231 """
232 raise NotImplementedError()
233
234 async def is_leader_from_status(self):
235 """
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.)
239
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
244 method.
245
246 """
247 app = self.name.split("/")[0]
248
249 c = client.ClientFacade.from_connection(self.connection)
250
251 status = await c.FullStatus(None)
252
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):
260 return False
261
262 if not status.applications[app].get('units'):
263 return False
264
265 if not status.applications[app]['units'].get(self.name):
266 return False
267
268 return status.applications[app]['units'][self.name].get('leader',
269 False)
270
271 async def get_metrics(self):
272 """Get metrics for the unit.
273
274 :return: Dictionary of metrics for this unit.
275
276 """
277 metrics = await self.model.get_metrics(self.tag)
278 return metrics[self.name]