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