Added a catch for the case where we call is_leader_from_status on a
[osm/N2VC.git] / juju / unit.py
1 import logging
2 from datetime import datetime
3
4 from . import model
5 from .client import client
6
7 log = logging.getLogger(__name__)
8
9
10 class Unit(model.ModelEntity):
11 @property
12 def agent_status(self):
13 """Returns the current agent status string.
14
15 """
16 return self.data['agent-status']['current']
17
18 @property
19 def agent_status_since(self):
20 """Get the time when the `agent_status` was last updated.
21
22 """
23 since = self.data['agent-status']['since']
24 # Juju gives us nanoseconds, but Python only supports microseconds
25 since = since[:26]
26 return datetime.strptime(since, "%Y-%m-%dT%H:%M:%S.%f")
27
28 @property
29 def agent_status_message(self):
30 """Get the agent status message.
31
32 """
33 return self.data['agent-status']['message']
34
35 @property
36 def workload_status(self):
37 """Returns the current workload status string.
38
39 """
40 return self.data['workload-status']['current']
41
42 @property
43 def workload_status_since(self):
44 """Get the time when the `workload_status` was last updated.
45
46 """
47 since = self.data['workload-status']['since']
48 # Juju gives us nanoseconds, but Python only supports microseconds
49 since = since[:26]
50 return datetime.strptime(since, "%Y-%m-%dT%H:%M:%S.%f")
51
52 @property
53 def workload_status_message(self):
54 """Get the workload status message.
55
56 """
57 return self.data['workload-status']['message']
58
59 @property
60 def tag(self):
61 return 'unit-%s' % self.name.replace('/', '-')
62
63 def add_storage(self, name, constraints=None):
64 """Add unit storage dynamically.
65
66 :param str name: Storage name, as specified by the charm
67 :param str constraints: Comma-separated list of constraints in the
68 form 'POOL,COUNT,SIZE'
69
70 """
71 pass
72
73 def collect_metrics(self):
74 """Collect metrics on this unit.
75
76 """
77 pass
78
79 async def destroy(self):
80 """Destroy this unit.
81
82 """
83 app_facade = client.ApplicationFacade()
84 app_facade.connect(self.connection)
85
86 log.debug(
87 'Destroying %s', self.name)
88
89 return await app_facade.DestroyUnits([self.name])
90 remove = destroy
91
92 def get_resources(self, details=False):
93 """Return resources for this unit.
94
95 :param bool details: Include detailed info about resources used by each
96 unit
97
98 """
99 pass
100
101 def resolved(self, retry=False):
102 """Mark unit errors resolved.
103
104 :param bool retry: Re-execute failed hooks
105
106 """
107 pass
108
109 async def run(self, command, timeout=None):
110 """Run command on this unit.
111
112 :param str command: The command to run
113 :param int timeout: Time to wait before command is considered failed
114
115 Returns a tuple containing the stdout, stderr, and return code
116 from the command.
117
118 """
119 action = client.ActionFacade()
120 action.connect(self.connection)
121
122 log.debug(
123 'Running `%s` on %s', command, self.name)
124
125 res = await action.Run(
126 [],
127 command,
128 [],
129 timeout,
130 [self.name],
131 )
132 return await self.model.wait_for_action(res.results[0].action.tag)
133
134 async def run_action(self, action_name, **params):
135 """Run an action on this unit.
136
137 :param str action_name: Name of action to run
138 :param \*\*params: Action parameters
139 :returns: An `juju.action.Action` instance.
140
141 Note that this only enqueues the action. You will need to call
142 ``action.wait()`` on the resulting `Action` instance if you wish
143 to block until the action is complete.
144 """
145 action_facade = client.ActionFacade()
146 action_facade.connect(self.connection)
147
148 log.debug('Starting action `%s` on %s', action_name, self.name)
149
150 res = await action_facade.Enqueue([client.Action(
151 name=action_name,
152 parameters=params,
153 receiver=self.tag,
154 )])
155 action = res.results[0].action
156 error = res.results[0].error
157 if error and error.code == 'not found':
158 raise ValueError('Action `%s` not found on %s' % (action_name,
159 self.name))
160 elif error:
161 raise Exception('Unknown action error: %s' % error.serialize())
162 action_id = action.tag[len('action-'):]
163 log.debug('Action started as %s', action_id)
164 # we can't use wait_for_new here because we don't
165 # consistently (ever?) get an "add" delta for the action
166 return await self.model._wait('action', action_id, None)
167
168 def scp(
169 self, source_path, user=None, destination_path=None, proxy=False,
170 scp_opts=None):
171 """Transfer files to this unit.
172
173 :param str source_path: Path of file(s) to transfer
174 :param str user: Remote username
175 :param str destination_path: Destination of transferred files on
176 remote machine
177 :param bool proxy: Proxy through the Juju API server
178 :param str scp_opts: Additional options to the `scp` command
179
180 """
181 pass
182
183 def set_meter_status(self):
184 """Set the meter status on this unit.
185
186 """
187 pass
188
189 def ssh(
190 self, command, user=None, proxy=False, ssh_opts=None):
191 """Execute a command over SSH on this unit.
192
193 :param str command: Command to execute
194 :param str user: Remote username
195 :param bool proxy: Proxy through the Juju API server
196 :param str ssh_opts: Additional options to the `ssh` command
197
198 """
199 pass
200
201 def status_history(self, num=20, utc=False):
202 """Get status history for this unit.
203
204 :param int num: Size of history backlog
205 :param bool utc: Display time as UTC in RFC3339 format
206
207 """
208 pass
209
210 async def is_leader_from_status(self):
211 """
212 Check to see if this unit is the leader. Returns True if so, and
213 False if it is not, or if leadership does not make sense
214 (e.g., there is no leader in this application.)
215
216 This method is a kluge that calls FullStatus in the
217 ClientFacade to get its information. Once
218 https://bugs.launchpad.net/juju/+bug/1643691 is resolved, we
219 should add a simple .is_leader property, and deprecate this
220 method.
221
222 """
223 app = self.name.split("/")[0]
224
225 c = client.ClientFacade()
226 c.connect(self.model.connection)
227
228 status = await c.FullStatus(None)
229
230 try:
231 return status.applications[app]['units'][self.name].get(
232 'leader', False)
233 except KeyError:
234 # FullStatus may be more up-to-date than the model
235 # referenced by this class. If this unit has been
236 # destroyed between the time the class was created and the
237 # time that we call this method, we'll get a KeyError. In
238 # that case, we simply return False, as a destroyed unit
239 # is not a leader.
240 return False