7
8
from landscape.lib.twisted_util import gather_results
8
9
from landscape.lib.bpickle import loads
9
10
from landscape.lib.log import log_failure
12
EC2_API = "http://169.254.169.254/latest"
11
from landscape.lib.fetch import fetch, FetchError
14
EC2_HOST = "169.254.169.254"
15
EC2_API = "http://%s/latest" % (EC2_HOST,)
15
18
class InvalidCredentialsError(Exception):
35
38
class Identity(object):
36
"""Maintains details about the identity of this Landscape client."""
39
"""Maintains details about the identity of this Landscape client.
41
@ivar secure_id: A server-provided ID for secure message exchange.
42
@ivar insecure_id: Non-secure server-provided ID, mainly used with
44
@ivar computer_title: See L{BrokerConfiguration}.
45
@ivar account_name: See L{BrokerConfiguration}.
46
@ivar registration_password: See L{BrokerConfiguration}.
38
49
secure_id = persist_property("secure-id")
39
50
insecure_id = persist_property("insecure-id")
42
53
registration_password = config_property("registration_password")
44
55
def __init__(self, config, persist):
57
@param config: A L{BrokerConfiguration} object, used to set the
58
C{computer_title}, C{account_name} and C{registration_password}
45
61
self._config = config
46
62
self._persist = persist.root_at("registration")
98
114
self._exchange.exchange()
101
def _extract_ec2_instance_data(self, raw_user_data, launch_index):
103
Given the raw string of EC2 User Data, parse it and return the dict of
104
instance data for this particular instance.
106
If the data can't be parsed, a debug message will be logged and None
110
user_data = loads(raw_user_data)
112
logging.debug("Got invalid user-data %r" % (raw_user_data,))
115
if not isinstance(user_data, dict):
116
logging.debug("user-data %r is not a dict" % (user_data,))
118
for key in "otps", "exchange-url", "ping-url":
119
if key not in user_data:
120
logging.debug("user-data %r doesn't have key %r."
123
if len(user_data["otps"]) <= launch_index:
124
logging.debug("user-data %r doesn't have OTP for launch index %d"
125
% (user_data, launch_index))
127
return {"otp": user_data["otps"][launch_index],
128
"exchange-url": user_data["exchange-url"],
129
"ping-url": user_data["ping-url"]}
131
117
def _fetch_ec2_data(self):
132
118
id = self._identity
133
119
if self._cloud and not id.secure_id:
170
156
self._ec2_data["launch_index"] = int(
171
157
self._ec2_data["launch_index"])
173
instance_data = self._extract_ec2_instance_data(
159
instance_data = _extract_ec2_instance_data(
174
160
raw_user_data, int(launch_index))
175
161
if instance_data is not None:
176
162
self._otp = instance_data["otp"]
191
177
registration_data.addErrback(log_error)
193
179
def _handle_exchange_done(self):
180
"""Registered handler for the C{"exchange-done"} event.
182
If we are not registered yet, schedule another message exchange.
184
The first exchange made us accept the message type "register", so
185
the next "pre-exchange" event will make L{_handle_pre_exchange}
186
queue a registration message for delivery.
194
188
if self.should_register() and not self._should_register:
195
# We received accepted-types (first exchange), so we now trigger
196
# the second exchange for registration
197
189
self._exchange.exchange()
199
191
def _handle_pre_exchange(self):
218
210
logging.info("Queueing message to register with OTP")
219
211
message = {"type": "register-cloud-vm",
220
212
"otp": self._otp,
221
"hostname": socket.gethostname(),
213
"hostname": socket.getfqdn(),
222
214
"account_name": None,
223
215
"registration_password": None,
229
221
"%r as an EC2 instance." % (id.account_name,))
230
222
message = {"type": "register-cloud-vm",
232
"hostname": socket.gethostname(),
224
"hostname": socket.getfqdn(),
233
225
"account_name": id.account_name,
234
226
"registration_password": \
235
227
id.registration_password}
246
238
"computer_title": id.computer_title,
247
239
"account_name": id.account_name,
248
240
"registration_password": id.registration_password,
249
"hostname": socket.gethostname()}
241
"hostname": socket.getfqdn()}
250
242
self._exchange.send(message)
252
244
self._reactor.fire("registration-failed")
254
246
def _handle_set_id(self, message):
247
"""Registered handler for the C{"set-id"} event.
256
249
Record and start using the secure and insecure IDs from the given
252
Fire C{"registration-done"} and C{"resynchronize-clients"}.
259
254
id = self._identity
260
255
id.secure_id = message.get("id")
301
296
def _failed(self):
302
297
self.deferred.errback(InvalidCredentialsError())
303
298
self._cancel_calls()
301
def _extract_ec2_instance_data(raw_user_data, launch_index):
303
Given the raw string of EC2 User Data, parse it and return the dict of
304
instance data for this particular instance.
306
If the data can't be parsed, a debug message will be logged and None
310
user_data = loads(raw_user_data)
312
logging.debug("Got invalid user-data %r" % (raw_user_data,))
315
if not isinstance(user_data, dict):
316
logging.debug("user-data %r is not a dict" % (user_data,))
318
for key in "otps", "exchange-url", "ping-url":
319
if key not in user_data:
320
logging.debug("user-data %r doesn't have key %r."
323
if len(user_data["otps"]) <= launch_index:
324
logging.debug("user-data %r doesn't have OTP for launch index %d"
325
% (user_data, launch_index))
327
return {"otp": user_data["otps"][launch_index],
328
"exchange-url": user_data["exchange-url"],
329
"ping-url": user_data["ping-url"]}
332
def _wait_for_network():
334
Keep trying to connect to the EC2 metadata server until it becomes
335
accessible or until five minutes pass.
337
This is necessary because the networking init script on Ubuntu is
338
asynchronous; the network may not actually be up by the time the
339
landscape-client init script is invoked.
348
s.connect((EC2_HOST, port))
351
except socket.error, e:
353
if time.time() - start > timeout:
357
def is_cloud_managed(fetch=fetch):
359
Return C{True} if the machine has been started by Landscape, i.e. if we can
360
find the expected data inside the EC2 user-data field.
364
raw_user_data = fetch(EC2_API + "/user-data",
366
launch_index = fetch(EC2_API + "/meta-data/ami-launch-index",
370
instance_data = _extract_ec2_instance_data(raw_user_data, int(launch_index))
371
return instance_data is not None