Initial import of N2VC
[osm/N2VC.git] / modules / libjuju / juju / unit.py
1 import logging
2
3 from dateutil.parser import parse as parse_date
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 parse_date(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 parse_date(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 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 res = await action.Run(
135 [],
136 command,
137 [],
138 timeout,
139 [self.name],
140 )
141 return await self.model.wait_for_action(res.results[0].action.tag)
142
143 async def run_action(self, action_name, **params):
144 """Run an action on this unit.
145
146 :param str action_name: Name of action to run
147 :param \*\*params: Action parameters
148 :returns: A :class:`juju.action.Action` instance.
149
150 Note that this only enqueues the action. You will need to call
151 ``action.wait()`` on the resulting `Action` instance if you wish
152 to block until the action is complete.
153
154 """
155 action_facade = client.ActionFacade.from_connection(self.connection)
156
157 log.debug('Starting action `%s` on %s', action_name, self.name)
158
159 res = await action_facade.Enqueue([client.Action(
160 name=action_name,
161 parameters=params,
162 receiver=self.tag,
163 )])
164 action = res.results[0].action
165 error = res.results[0].error
166 if error and error.code == 'not found':
167 raise ValueError('Action `%s` not found on %s' % (action_name,
168 self.name))
169 elif error:
170 raise Exception('Unknown action error: %s' % error.serialize())
171 action_id = action.tag[len('action-'):]
172 log.debug('Action started as %s', action_id)
173 # we mustn't use wait_for_action because that blocks until the
174 # action is complete, rather than just being in the model
175 return await self.model._wait_for_new('action', action_id)
176
177 async def scp_to(self, source, destination, user='ubuntu', proxy=False,
178 scp_opts=''):
179 """Transfer files to this unit.
180
181 :param str source: Local path of file(s) to transfer
182 :param str destination: Remote destination of transferred files
183 :param str user: Remote username
184 :param bool proxy: Proxy through the Juju API server
185 :param str scp_opts: Additional options to the `scp` command
186 """
187 await self.machine.scp_to(source, destination, user=user, proxy=proxy,
188 scp_opts=scp_opts)
189
190 async def scp_from(self, source, destination, user='ubuntu', proxy=False,
191 scp_opts=''):
192 """Transfer files from this unit.
193
194 :param str source: Remote path of file(s) to transfer
195 :param str destination: Local destination of transferred files
196 :param str user: Remote username
197 :param bool proxy: Proxy through the Juju API server
198 :param str scp_opts: Additional options to the `scp` command
199 """
200 await self.machine.scp_from(source, destination, user=user,
201 proxy=proxy, scp_opts=scp_opts)
202
203 def set_meter_status(self):
204 """Set the meter status on this unit.
205
206 """
207 raise NotImplementedError()
208
209 def ssh(
210 self, command, user=None, proxy=False, ssh_opts=None):
211 """Execute a command over SSH on this unit.
212
213 :param str command: Command to execute
214 :param str user: Remote username
215 :param bool proxy: Proxy through the Juju API server
216 :param str ssh_opts: Additional options to the `ssh` command
217
218 """
219 raise NotImplementedError()
220
221 def status_history(self, num=20, utc=False):
222 """Get status history for this unit.
223
224 :param int num: Size of history backlog
225 :param bool utc: Display time as UTC in RFC3339 format
226
227 """
228 raise NotImplementedError()
229
230 async def is_leader_from_status(self):
231 """
232 Check to see if this unit is the leader. Returns True if so, and
233 False if it is not, or if leadership does not make sense
234 (e.g., there is no leader in this application.)
235
236 This method is a kluge that calls FullStatus in the
237 ClientFacade to get its information. Once
238 https://bugs.launchpad.net/juju/+bug/1643691 is resolved, we
239 should add a simple .is_leader property, and deprecate this
240 method.
241
242 """
243 app = self.name.split("/")[0]
244
245 c = client.ClientFacade.from_connection(self.connection)
246
247 status = await c.FullStatus(None)
248
249 # FullStatus may be more up to date than our model, and the
250 # unit may have gone away, or we may be doing something silly,
251 # like trying to fetch leadership for a subordinate, which
252 # will not be filed where we expect in the model. In those
253 # cases, we may simply return False, as a nonexistent or
254 # subordinate unit is not a leader.
255 if not status.applications.get(app):
256 return False
257
258 if not status.applications[app].get('units'):
259 return False
260
261 if not status.applications[app]['units'].get(self.name):
262 return False
263
264 return status.applications[app]['units'][self.name].get('leader',
265 False)
266
267 async def get_metrics(self):
268 """Get metrics for the unit.
269
270 :return: Dictionary of metrics for this unit.
271
272 """
273 metrics = await self.model.get_metrics(self.tag)
274 return metrics[self.name]