ea51345c19292ac5fb26f0a74c96e30149a9955d
[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 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 :returns: A :class:`juju.action.Action` instance.
110
111 Returns a tuple containing the stdout, stderr, and return code
112 from the command.
113
114 """
115 action = client.ActionFacade()
116 action.connect(self.connection)
117
118 log.debug(
119 'Running `%s` on %s', command, self.name)
120
121 res = await action.Run(
122 [],
123 command,
124 [],
125 timeout,
126 [self.name],
127 )
128 return await self.model.wait_for_action(res.results[0].action.tag)
129
130 async def run_action(self, action_name, **params):
131 """Run an action on this unit.
132
133 :param str action_name: Name of action to run
134 :param \*\*params: Action parameters
135 :returns: A :class:`juju.action.Action` instance.
136
137 Note that this only enqueues the action. You will need to call
138 ``action.wait()`` on the resulting `Action` instance if you wish
139 to block until the action is complete.
140
141 """
142 action_facade = client.ActionFacade()
143 action_facade.connect(self.connection)
144
145 log.debug('Starting action `%s` on %s', action_name, self.name)
146
147 res = await action_facade.Enqueue([client.Action(
148 name=action_name,
149 parameters=params,
150 receiver=self.tag,
151 )])
152 action = res.results[0].action
153 error = res.results[0].error
154 if error and error.code == 'not found':
155 raise ValueError('Action `%s` not found on %s' % (action_name,
156 self.name))
157 elif error:
158 raise Exception('Unknown action error: %s' % error.serialize())
159 action_id = action.tag[len('action-'):]
160 log.debug('Action started as %s', action_id)
161 # we mustn't use wait_for_action because that blocks until the
162 # action is complete, rather than just being in the model
163 return await self.model._wait_for_new('action', action_id)
164
165 def scp(
166 self, source_path, user=None, destination_path=None, proxy=False,
167 scp_opts=None):
168 """Transfer files to this unit.
169
170 :param str source_path: Path of file(s) to transfer
171 :param str user: Remote username
172 :param str destination_path: Destination of transferred files on
173 remote machine
174 :param bool proxy: Proxy through the Juju API server
175 :param str scp_opts: Additional options to the `scp` command
176
177 """
178 pass
179
180 def set_meter_status(self):
181 """Set the meter status on this unit.
182
183 """
184 pass
185
186 def ssh(
187 self, command, user=None, proxy=False, ssh_opts=None):
188 """Execute a command over SSH on this unit.
189
190 :param str command: Command to execute
191 :param str user: Remote username
192 :param bool proxy: Proxy through the Juju API server
193 :param str ssh_opts: Additional options to the `ssh` command
194
195 """
196 pass
197
198 def status_history(self, num=20, utc=False):
199 """Get status history for this unit.
200
201 :param int num: Size of history backlog
202 :param bool utc: Display time as UTC in RFC3339 format
203
204 """
205 pass
206
207 async def is_leader_from_status(self):
208 """
209 Check to see if this unit is the leader. Returns True if so, and
210 False if it is not, or if leadership does not make sense
211 (e.g., there is no leader in this application.)
212
213 This method is a kluge that calls FullStatus in the
214 ClientFacade to get its information. Once
215 https://bugs.launchpad.net/juju/+bug/1643691 is resolved, we
216 should add a simple .is_leader property, and deprecate this
217 method.
218
219 """
220 app = self.name.split("/")[0]
221
222 c = client.ClientFacade()
223 c.connect(self.model.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 metrics = await self.model.get_metrics(self.tag)
247 return metrics[self.name]