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