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