~ubuntu-branches/ubuntu/karmic/landscape-client/karmic-updates

« back to all changes in this revision

Viewing changes to landscape/broker/registration.py

  • Committer: Bazaar Package Importer
  • Author(s): Mathias Gug, Free Ekanayaka
  • Date: 2009-07-22 14:54:50 UTC
  • mfrom: (1.1.9 upstream)
  • Revision ID: james.westby@ubuntu.com-20090722145450-pvbp13gh8734c8ft
Tags: 1.3.2.2-0ubuntu0.9.10.1
[ Free Ekanayaka ]
* New upstream release:
  - Include the README file in landscape-client (LP: #396260)
  - Fix client capturing stderr from run_command when constructing
    hash-id-databases url (LP: #397480)
  - Use substvars to conditionally depend on update-motd or
    libpam-modules (LP: #393454)
  - Fix reporting wrong version to the server (LP: #391225)
  - The init script does not wait for the network to be available
    before checking for EC2 user data (LP: #383336)
  - When the broker is restarted by the watchdog, the state of the client
    is inconsistent (LP: #380633)
  - Package stays unknown forever in the client with hash-id-databases
    support (LP: #381356)
  - Standard error not captured when calling smart-update (LP: #387441)
  - Changer calls reporter without switching groups, just user (LP: #388092)
  - Run smart update in the package-reporter instead of having a cronjob (LP: #362355)
  - Package changer does not inherit proxy settings (LP: #381241)
  - The ./test script doesn't work in landscape-client (LP: #381613)
  - The source package should build on all supported releases (LP: #385098)
  - Strip smart update's output (LP: #387331)
  - The fetch() timeout isn't based on activity (#389224)
  - Client can use a UUID of "None" when fetching the hash-id-database (LP: #381291)
  - Registration should use the fqdn rather than just the hostname (LP: #385730)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
 
 
2
import time
2
3
import logging
3
4
import socket
4
5
 
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
10
 
 
11
 
 
12
 
EC2_API = "http://169.254.169.254/latest"
 
11
from landscape.lib.fetch import fetch, FetchError
 
12
 
 
13
 
 
14
EC2_HOST = "169.254.169.254"
 
15
EC2_API = "http://%s/latest" % (EC2_HOST,)
13
16
 
14
17
 
15
18
class InvalidCredentialsError(Exception):
33
36
 
34
37
 
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.
 
40
 
 
41
    @ivar secure_id: A server-provided ID for secure message exchange.
 
42
    @ivar insecure_id: Non-secure server-provided ID, mainly used with
 
43
        the ping server.
 
44
    @ivar computer_title: See L{BrokerConfiguration}.
 
45
    @ivar account_name: See L{BrokerConfiguration}.
 
46
    @ivar registration_password: See L{BrokerConfiguration}.
 
47
    """
37
48
 
38
49
    secure_id = persist_property("secure-id")
39
50
    insecure_id = persist_property("insecure-id")
42
53
    registration_password = config_property("registration_password")
43
54
 
44
55
    def __init__(self, config, persist):
 
56
        """
 
57
        @param config: A L{BrokerConfiguration} object, used to set the
 
58
            C{computer_title}, C{account_name} and C{registration_password}
 
59
            instance variables.
 
60
        """
45
61
        self._config = config
46
62
        self._persist = persist.root_at("registration")
47
63
 
98
114
        self._exchange.exchange()
99
115
        return result
100
116
 
101
 
    def _extract_ec2_instance_data(self, raw_user_data, launch_index):
102
 
        """
103
 
        Given the raw string of EC2 User Data, parse it and return the dict of
104
 
        instance data for this particular instance.
105
 
 
106
 
        If the data can't be parsed, a debug message will be logged and None
107
 
        will be returned.
108
 
        """
109
 
        try:
110
 
            user_data = loads(raw_user_data)
111
 
        except ValueError:
112
 
            logging.debug("Got invalid user-data %r" % (raw_user_data,))
113
 
            return
114
 
 
115
 
        if not isinstance(user_data, dict):
116
 
            logging.debug("user-data %r is not a dict" % (user_data,))
117
 
            return
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."
121
 
                              % (user_data, key))
122
 
                return
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))
126
 
            return
127
 
        return {"otp": user_data["otps"][launch_index],
128
 
                "exchange-url": user_data["exchange-url"],
129
 
                "ping-url": user_data["ping-url"]}
130
 
 
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"])
172
158
 
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)
192
178
 
193
179
    def _handle_exchange_done(self):
 
180
        """Registered handler for the C{"exchange-done"} event.
 
181
 
 
182
        If we are not registered yet, schedule another message exchange.
 
183
 
 
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.
 
187
        """
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()
198
190
 
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,
224
216
                               }
229
221
                                 "%r as an EC2 instance." % (id.account_name,))
230
222
                    message = {"type": "register-cloud-vm",
231
223
                               "otp": None,
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)
251
243
            else:
252
244
                self._reactor.fire("registration-failed")
253
245
 
254
246
    def _handle_set_id(self, message):
255
 
        """
 
247
        """Registered handler for the C{"set-id"} event.
 
248
        
256
249
        Record and start using the secure and insecure IDs from the given
257
250
        message.
 
251
 
 
252
        Fire C{"registration-done"} and C{"resynchronize-clients"}.
258
253
        """
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()
 
299
 
 
300
 
 
301
def _extract_ec2_instance_data(raw_user_data, launch_index):
 
302
    """
 
303
    Given the raw string of EC2 User Data, parse it and return the dict of
 
304
    instance data for this particular instance.
 
305
 
 
306
    If the data can't be parsed, a debug message will be logged and None
 
307
    will be returned.
 
308
    """
 
309
    try:
 
310
        user_data = loads(raw_user_data)
 
311
    except ValueError:
 
312
        logging.debug("Got invalid user-data %r" % (raw_user_data,))
 
313
        return
 
314
 
 
315
    if not isinstance(user_data, dict):
 
316
        logging.debug("user-data %r is not a dict" % (user_data,))
 
317
        return
 
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."
 
321
                          % (user_data, key))
 
322
            return
 
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))
 
326
        return
 
327
    return {"otp": user_data["otps"][launch_index],
 
328
            "exchange-url": user_data["exchange-url"],
 
329
            "ping-url": user_data["ping-url"]}
 
330
 
 
331
 
 
332
def _wait_for_network():
 
333
    """
 
334
    Keep trying to connect to the EC2 metadata server until it becomes
 
335
    accessible or until five minutes pass.
 
336
 
 
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.
 
340
    """
 
341
    timeout = 5*60
 
342
    port = 80
 
343
 
 
344
    start = time.time()
 
345
    while True:
 
346
        s = socket.socket()
 
347
        try:
 
348
            s.connect((EC2_HOST, port))
 
349
            s.close()
 
350
            return
 
351
        except socket.error, e:
 
352
            time.sleep(1)
 
353
            if time.time() - start > timeout:
 
354
                break
 
355
                
 
356
 
 
357
def is_cloud_managed(fetch=fetch):
 
358
    """
 
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.
 
361
    """
 
362
    _wait_for_network()
 
363
    try:
 
364
        raw_user_data = fetch(EC2_API + "/user-data",
 
365
                              connect_timeout=5)
 
366
        launch_index = fetch(EC2_API + "/meta-data/ami-launch-index",
 
367
                             connect_timeout=5)
 
368
    except FetchError:
 
369
        return False
 
370
    instance_data = _extract_ec2_instance_data(raw_user_data, int(launch_index))
 
371
    return instance_data is not None