Merge lp:~free.ekanayaka/landscape-client/drop-cloud-registration into lp:~landscape/landscape-client/trunk

Proposed by Free Ekanayaka
Status: Merged
Approved by: Free Ekanayaka
Approved revision: 783
Merged at revision: 778
Proposed branch: lp:~free.ekanayaka/landscape-client/drop-cloud-registration
Merge into: lp:~landscape/landscape-client/trunk
Diff against target: 1179 lines (+12/-941)
11 files modified
debian/cloud-default.conf (+0/-7)
debian/landscape-client.init (+2/-18)
debian/landscape-client.install (+0/-1)
landscape/broker/config.py (+0/-6)
landscape/broker/registration.py (+3/-210)
landscape/broker/tests/test_registration.py (+3/-669)
landscape/configuration.py (+1/-1)
landscape/message_schemas.py (+3/-0)
landscape/tests/test_configuration.py (+0/-16)
scripts/landscape-is-cloud-managed (+0/-12)
setup.py (+0/-1)
To merge this branch: bzr merge lp:~free.ekanayaka/landscape-client/drop-cloud-registration
Reviewer Review Type Date Requested Status
Benji York (community) Approve
Geoff Teale (community) Approve
Review via email: mp+226993@code.launchpad.net

Commit message

Drop the cloud registration code and associated tests.

Description of the change

Drop the cloud registration code and associated tests.

To post a comment you must log in.
Revision history for this message
Geoff Teale (tealeg) wrote :

+1 - I see a lot of lint output, might want to take a look.

review: Approve
Revision history for this message
Benji York (benji) wrote :

So much deleteed code! It's a thing of beauty.

=== modified file 'landscape/broker/tests/test_registration.py'
 from landscape.broker.registration import (
- InvalidCredentialsError, RegistrationHandler, is_cloud_managed, EC2_HOST,
- EC2_API, Identity)
+ InvalidCredentialsError, Identity)

I can't remember if we're uptight about alphebetical import order, but
if we are, look above.

=== modified file 'landscape/message_schemas.py'
--- landscape/message_schemas.py 2014-06-09 08:18:05 +0000
+++ landscape/message_schemas.py 2014-07-16 10:28:53 +0000
@@ -211,6 +211,8 @@
     {"otp": Bytes()})

+# XXX The register-cloud-vm message is obsolete, it's kept around just to not
+# break older LDS releases that import it. Eventually it shall be dropped.
 REGISTER_CLOUD_VM = Message(
     "register-cloud-vm",
     {"hostname": Unicode(),

I guess we can bzr-blame the above, but a date would make it obvious
when we can drop the noop.

review: Approve
Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

Benji: we don't maintain imports in alphabetical order for now, but it'd be probably a good thing to do, together to having them one per line instead of smashed together. That takes increases a bit the lines count but it easier to read and maintain (e.g. conflicts). Might be a good topic for the sprint if you care to add it.

About the date, I've added the last LDS version that has this imports: 14.07.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed file 'debian/cloud-default.conf'
2--- debian/cloud-default.conf 2009-03-28 19:04:34 +0000
3+++ debian/cloud-default.conf 1970-01-01 00:00:00 +0000
4@@ -1,7 +0,0 @@
5-[client]
6-cloud = True
7-url = https://landscape.canonical.com/message-system
8-data_path = /var/lib/landscape/client
9-ping_url = http://landscape.canonical.com/ping
10-include_manager_plugins = ScriptExecution
11-script_users = ALL
12
13=== modified file 'debian/landscape-client.init'
14--- debian/landscape-client.init 2012-09-05 15:37:17 +0000
15+++ debian/landscape-client.init 2014-07-17 06:52:14 +0000
16@@ -31,24 +31,8 @@
17 # This $RUN check should match the semantics of
18 # l.sysvconfig.SysVConfig.is_configured_to_run.
19 if [ $RUN -eq 0 ]; then
20- if [ $CLOUD -eq 1 ]; then
21- if landscape-is-cloud-managed; then
22- # Install the cloud default configuration file
23- cp /usr/share/landscape/cloud-default.conf /etc/landscape/client.conf
24- # Override default file for not going in this conditional again at
25- # next startup
26- sed -i "s/^RUN=.*/RUN=1/" $LANDSCAPE_DEFAULTS
27- if ! grep -q "^RUN=" $LANDSCAPE_DEFAULTS; then
28- echo "RUN=1" >> $LANDSCAPE_DEFAULTS
29- fi
30- else
31- echo "$NAME is not configured, please run landscape-config."
32- exit 0
33- fi
34- else
35- echo "$NAME is not configured, please run landscape-config."
36- exit 0
37- fi
38+ echo "$NAME is not configured, please run landscape-config."
39+ exit 0
40 fi
41 }
42
43
44=== modified file 'debian/landscape-client.install'
45--- debian/landscape-client.install 2013-05-16 09:15:13 +0000
46+++ debian/landscape-client.install 2014-07-17 06:52:14 +0000
47@@ -7,7 +7,6 @@
48 usr/bin/landscape-package-changer
49 usr/bin/landscape-package-reporter
50 usr/bin/landscape-release-upgrader
51-usr/bin/landscape-is-cloud-managed
52 usr/bin/landscape-dbus-proxy
53 usr/share/landscape/cloud-default.conf
54 usr/lib/landscape
55
56=== modified file 'landscape/broker/config.py'
57--- landscape/broker/config.py 2014-03-25 15:21:52 +0000
58+++ landscape/broker/config.py 2014-07-17 06:52:14 +0000
59@@ -32,8 +32,6 @@
60 - C{urgent_exchange_interval} (C{1*60})
61 - C{http_proxy}
62 - C{https_proxy}
63- - C{cloud}
64- - C{otp}
65 - C{provisioning_otp}
66 """
67 parser = super(BrokerConfiguration, self).make_parser()
68@@ -60,10 +58,6 @@
69 help="The URL of the HTTP proxy, if one is needed.")
70 parser.add_option("--https-proxy", metavar="URL",
71 help="The URL of the HTTPS proxy, if one is needed.")
72- parser.add_option("--cloud", action="store_true",
73- help="Set this if your computer is in an EC2 cloud.")
74- parser.add_option("--otp", default="",
75- help="The OTP to use in cloud configuration.")
76 parser.add_option("--access-group", default="",
77 help="Suggested access group for this computer.")
78 parser.add_option("--tags",
79
80=== modified file 'landscape/broker/registration.py'
81--- landscape/broker/registration.py 2014-06-19 10:51:57 +0000
82+++ landscape/broker/registration.py 2014-07-17 06:52:14 +0000
83@@ -8,25 +8,16 @@
84 credentials yet and that the server accepts registration messages, so it
85 will craft an appropriate one and send it out.
86 """
87-import time
88 import logging
89-import socket
90
91 from twisted.internet.defer import Deferred
92
93-from landscape.lib.bpickle import loads
94-from landscape.lib.log import log_failure
95 from landscape.lib.juju import get_juju_info
96-from landscape.lib.fetch import fetch, FetchError
97 from landscape.lib.tag import is_valid_tag_list
98 from landscape.lib.network import get_fqdn
99 from landscape.lib.vm_info import get_vm_info, get_container_info
100
101
102-EC2_HOST = "169.254.169.254"
103-EC2_API = "http://%s/latest" % (EC2_HOST,)
104-
105-
106 class InvalidCredentialsError(Exception):
107 """
108 Raised when an invalid account title and/or registration key
109@@ -99,7 +90,6 @@
110 self._exchange = exchange
111 self._pinger = pinger
112 self._message_store = message_store
113- self._reactor.call_on("run", self._fetch_ec2_data)
114 self._reactor.call_on("run", self._get_juju_data)
115 self._reactor.call_on("pre-exchange", self._handle_pre_exchange)
116 self._reactor.call_on("exchange-done", self._handle_exchange_done)
117@@ -109,21 +99,15 @@
118 self._handle_registration)
119 self._should_register = None
120 self._fetch_async = fetch_async
121- self._otp = None
122 self._ec2_data = None
123 self._juju_data = None
124
125 def should_register(self):
126 id = self._identity
127 if id.secure_id:
128- # We already have a secure ID, no need to register
129- logging.info("Machine already has a secure-id. Skipping "
130- "registration.")
131 return False
132
133- if self._config.cloud:
134- return self._message_store.accepts("register-cloud-vm")
135- elif self._config.provisioning_otp:
136+ if self._config.provisioning_otp:
137 return self._message_store.accepts("register-provisioned-machine")
138
139 return bool(id.computer_title and id.account_name
140@@ -157,94 +141,6 @@
141 """
142 return self._fetch_async(EC2_API + path).addCallback(accumulate.append)
143
144- def _fetch_ec2_data(self):
145- """Retrieve available EC2 information, if in a EC2 compatible cloud."""
146- id = self._identity
147- if self._config.cloud and not id.secure_id:
148- # Fetch data from the EC2 API, to be used later in the registration
149- # process
150- # We ignore errors from user-data because it's common for the
151- # URL to return a 404 when the data is unavailable.
152- ec2_data = []
153- deferred = self._fetch_async(EC2_API + "/user-data").addErrback(
154- log_failure).addCallback(ec2_data.append)
155- paths = [
156- "/meta-data/instance-id",
157- "/meta-data/reservation-id",
158- "/meta-data/local-hostname",
159- "/meta-data/public-hostname",
160- "/meta-data/ami-launch-index",
161- "/meta-data/ami-id",
162- "/meta-data/local-ipv4",
163- "/meta-data/public-ipv4"]
164- # We're not using a DeferredList here because we want to keep the
165- # number of connections to the backend minimal. See lp:567515.
166- for path in paths:
167- deferred.addCallback(
168- lambda ignore, path=path: self._get_data(path, ec2_data))
169- # Special case the ramdisk retrieval, because it may not be present
170- deferred.addCallback(
171- lambda ignore: self._fetch_async(
172- EC2_API + "/meta-data/ramdisk-id").addErrback(log_failure))
173- deferred.addCallback(ec2_data.append)
174- # And same for kernel
175- deferred.addCallback(
176- lambda ignore: self._fetch_async(
177- EC2_API + "/meta-data/kernel-id").addErrback(log_failure))
178- deferred.addCallback(ec2_data.append)
179-
180- def record_data(ignore):
181- """Record the instance data returned by the EC2 API."""
182- (raw_user_data, instance_key, reservation_key,
183- local_hostname, public_hostname, launch_index,
184- ami_key, local_ip, public_ip, ramdisk_key,
185- kernel_key) = ec2_data
186- self._ec2_data = {
187- "instance_key": instance_key,
188- "reservation_key": reservation_key,
189- "local_hostname": local_hostname,
190- "public_hostname": public_hostname,
191- "launch_index": launch_index,
192- "kernel_key": kernel_key,
193- "ramdisk_key": ramdisk_key,
194- "image_key": ami_key,
195- "public_ipv4": public_ip,
196- "local_ipv4": local_ip}
197- for k, v in self._ec2_data.items():
198- if v is None and k in ("ramdisk_key", "kernel_key"):
199- continue
200- self._ec2_data[k] = v.decode("utf-8")
201- self._ec2_data["launch_index"] = int(
202- self._ec2_data["launch_index"])
203-
204- if self._config.otp:
205- self._otp = self._config.otp
206- return
207- instance_data = _extract_ec2_instance_data(
208- raw_user_data, int(launch_index))
209- if instance_data is not None:
210- self._otp = instance_data["otp"]
211- exchange_url = instance_data["exchange-url"]
212- ping_url = instance_data["ping-url"]
213- self._exchange._transport.set_url(exchange_url)
214- self._config.url = exchange_url
215- self._config.ping_url = ping_url
216- if "ssl-ca-certificate" in instance_data:
217- from landscape.configuration import \
218- store_public_key_data
219- public_key_file = store_public_key_data(
220- self._config, instance_data["ssl-ca-certificate"])
221- self._config.ssl_public_key = public_key_file
222- self._exchange._transport._pubkey = public_key_file
223- self._config.write()
224-
225- def log_error(error):
226- log_failure(error, msg="Got error while fetching meta-data: %r"
227- % (error.value,))
228-
229- deferred.addCallback(record_data)
230- deferred.addErrback(log_error)
231-
232 def _handle_exchange_done(self):
233 """Registered handler for the C{"exchange-done"} event.
234
235@@ -264,12 +160,8 @@
236 message with the server.
237
238 A computer can fall into several categories:
239- - a "cloud VM"
240 - a "normal" computer
241 - a "provisionned machine".
242-
243- Furthermore, Cloud VMs can be registered with either a One Time
244- Password (OTP), or with a normal registration password.
245 """
246 registration_failed = False
247
248@@ -293,10 +185,9 @@
249
250 if not is_valid_tag_list(tags):
251 tags = None
252- logging.error("Invalid tags provided for cloud registration.")
253+ logging.error("Invalid tags provided for registration.")
254
255 message = {"type": None, # either "register" or "register-cloud-vm"
256- "otp": None,
257 "hostname": get_fqdn(),
258 "account_name": identity.account_name,
259 "registration_password": None,
260@@ -306,28 +197,7 @@
261 if group:
262 message["access_group"] = group
263
264- if self._config.cloud and self._ec2_data is not None:
265- # This is the "cloud VM" case.
266- message["type"] = "register-cloud-vm"
267-
268- message.update(self._ec2_data)
269- if self._otp:
270- logging.info("Queueing message to register with OTP")
271- message["otp"] = self._otp
272-
273- elif account_name:
274- with_tags = "and tags %s " % tags if tags else ""
275- with_group = "in access group '%s' " % group if group else ""
276- logging.info(
277- u"Queueing message to register with account %r %s%s"
278- u"as an EC2 instance." % (
279- account_name, with_group, with_tags))
280- message["registration_password"] = registration_key
281-
282- else:
283- registration_failed = True
284-
285- elif account_name:
286+ if account_name:
287 # The computer is a normal computer, possibly a container.
288 with_word = "with" if bool(registration_key) else "without"
289 with_tags = "and tags %s " % tags if tags else ""
290@@ -433,80 +303,3 @@
291 def _failed(self):
292 self.deferred.errback(InvalidCredentialsError())
293 self._cancel_calls()
294-
295-
296-def _extract_ec2_instance_data(raw_user_data, launch_index):
297- """
298- Given the raw string of EC2 User Data, parse it and return the dict of
299- instance data for this particular instance.
300-
301- If the data can't be parsed, a debug message will be logged and None
302- will be returned.
303- """
304- try:
305- user_data = loads(raw_user_data)
306- except ValueError:
307- logging.debug("Got invalid user-data %r" % (raw_user_data,))
308- return
309-
310- if not isinstance(user_data, dict):
311- logging.debug("user-data %r is not a dict" % (user_data,))
312- return
313- for key in "otps", "exchange-url", "ping-url":
314- if key not in user_data:
315- logging.debug("user-data %r doesn't have key %r."
316- % (user_data, key))
317- return
318- if len(user_data["otps"]) <= launch_index:
319- logging.debug("user-data %r doesn't have OTP for launch index %d"
320- % (user_data, launch_index))
321- return
322- instance_data = {"otp": user_data["otps"][launch_index],
323- "exchange-url": user_data["exchange-url"],
324- "ping-url": user_data["ping-url"]}
325- if "ssl-ca-certificate" in user_data:
326- instance_data["ssl-ca-certificate"] = user_data["ssl-ca-certificate"]
327- return instance_data
328-
329-
330-def _wait_for_network():
331- """
332- Keep trying to connect to the EC2 metadata server until it becomes
333- accessible or until five minutes pass.
334-
335- This is necessary because the networking init script on Ubuntu is
336- asynchronous; the network may not actually be up by the time the
337- landscape-client init script is invoked.
338- """
339- timeout = 5 * 60
340- port = 80
341-
342- start = time.time()
343- while True:
344- s = socket.socket()
345- try:
346- s.connect((EC2_HOST, port))
347- s.close()
348- return
349- except socket.error:
350- time.sleep(1)
351- if time.time() - start > timeout:
352- break
353-
354-
355-def is_cloud_managed(fetch=fetch):
356- """
357- Return C{True} if the machine has been started by Landscape, i.e. if we can
358- find the expected data inside the EC2 user-data field.
359- """
360- _wait_for_network()
361- try:
362- raw_user_data = fetch(EC2_API + "/user-data",
363- connect_timeout=5)
364- launch_index = fetch(EC2_API + "/meta-data/ami-launch-index",
365- connect_timeout=5)
366- except FetchError:
367- return False
368- instance_data = _extract_ec2_instance_data(
369- raw_user_data, int(launch_index))
370- return instance_data is not None
371
372=== modified file 'landscape/broker/tests/test_registration.py'
373--- landscape/broker/tests/test_registration.py 2014-06-20 06:06:51 +0000
374+++ landscape/broker/tests/test_registration.py 2014-07-17 06:52:14 +0000
375@@ -1,24 +1,15 @@
376 import json
377-import os
378 import logging
379-import pycurl
380 import socket
381
382-from twisted.internet.defer import succeed, fail
383-
384 from landscape.broker.registration import (
385- InvalidCredentialsError, RegistrationHandler, is_cloud_managed, EC2_HOST,
386- EC2_API, Identity)
387+ InvalidCredentialsError, Identity)
388
389-from landscape.broker.config import BrokerConfiguration
390 from landscape.tests.helpers import LandscapeTest
391 from landscape.broker.tests.helpers import (
392 BrokerConfigurationHelper, RegistrationHelper)
393-from landscape.lib.bpickle import dumps
394-from landscape.lib.fetch import HTTPCodeError, FetchError
395 from landscape.lib.persist import Persist
396 from landscape.lib.vm_info import get_vm_info
397-from landscape.configuration import print_text
398
399
400 class IdentityTest(LandscapeTest):
401@@ -250,8 +241,7 @@
402 If the admin has defined tags for this computer, but they are not
403 valid, we drop them, and report an error.
404 """
405- self.log_helper.ignore_errors("Invalid tags provided for cloud "
406- "registration")
407+ self.log_helper.ignore_errors("Invalid tags provided for registration")
408 self.mstore.set_accepted_types(["register"])
409 self.config.computer_title = "Computer Title"
410 self.config.account_name = "account_name"
411@@ -261,8 +251,7 @@
412 messages = self.mstore.get_pending_messages()
413 self.assertIs(None, messages[0]["tags"])
414 self.assertEqual(self.logfile.getvalue().strip(),
415- "ERROR: Invalid tags provided for cloud "
416- "registration.\n "
417+ "ERROR: Invalid tags provided for registration.\n "
418 "INFO: Queueing message to register with account "
419 "'account_name' with a password.\n "
420 "INFO: Sending registration message to exchange.")
421@@ -588,661 +577,6 @@
422 self.assertIn(expected2, juju_info)
423
424
425-class CloudRegistrationHandlerTest(RegistrationHandlerTestBase):
426-
427- cloud = True
428-
429- def setUp(self):
430- super(CloudRegistrationHandlerTest, self).setUp()
431- self.query_results = {}
432-
433- def fetch_stub(url):
434- value = self.query_results[url]
435- if isinstance(value, Exception):
436- return fail(value)
437- else:
438- return succeed(value)
439-
440- self.fetch_func = fetch_stub
441-
442- def get_user_data(self, otps=None,
443- exchange_url="https://example.com/message-system",
444- ping_url="http://example.com/ping",
445- ssl_ca_certificate=None):
446- if otps is None:
447- otps = ["otp1"]
448- user_data = {"otps": otps, "exchange-url": exchange_url,
449- "ping-url": ping_url}
450- if ssl_ca_certificate is not None:
451- user_data["ssl-ca-certificate"] = ssl_ca_certificate
452- return user_data
453-
454- def prepare_query_results(
455- self, user_data=None, instance_key="key1", launch_index=0,
456- local_hostname="ooga.local", public_hostname="ooga.amazon.com",
457- reservation_key=u"res1", ramdisk_key=u"ram1", kernel_key=u"kernel1",
458- image_key=u"image1", local_ip="10.0.0.1", public_ip="10.0.0.2",
459- ssl_ca_certificate=None):
460- if user_data is None:
461- user_data = self.get_user_data(
462- ssl_ca_certificate=ssl_ca_certificate)
463- if not isinstance(user_data, Exception):
464- user_data = dumps(user_data)
465- api_base = "http://169.254.169.254/latest"
466- self.query_results.clear()
467- for url_suffix, value in [
468- ("/user-data", user_data),
469- ("/meta-data/instance-id", instance_key),
470- ("/meta-data/reservation-id", reservation_key),
471- ("/meta-data/local-hostname", local_hostname),
472- ("/meta-data/public-hostname", public_hostname),
473- ("/meta-data/ami-launch-index", str(launch_index)),
474- ("/meta-data/kernel-id", kernel_key),
475- ("/meta-data/ramdisk-id", ramdisk_key),
476- ("/meta-data/ami-id", image_key),
477- ("/meta-data/local-ipv4", local_ip),
478- ("/meta-data/public-ipv4", public_ip),
479- ]:
480- self.query_results[api_base + url_suffix] = value
481-
482- def prepare_cloud_registration(self, account_name=None,
483- registration_key=None, tags=None,
484- access_group=None):
485- # Set things up so that the client thinks it should register
486- self.mstore.set_accepted_types(list(self.mstore.get_accepted_types())
487- + ["register-cloud-vm"])
488- self.config.account_name = account_name
489- self.config.registration_key = registration_key
490- self.config.computer_title = None
491- self.config.tags = tags
492- self.config.access_group = access_group
493- self.identity.secure_id = None
494- self.assertTrue(self.handler.should_register())
495-
496- def get_expected_cloud_message(self, **kwargs):
497- """
498- Return the message which is expected from a similar call to
499- L{get_registration_handler_for_cloud}.
500- """
501- message = dict(type="register-cloud-vm",
502- otp="otp1",
503- hostname="ooga.local",
504- local_hostname="ooga.local",
505- public_hostname="ooga.amazon.com",
506- instance_key=u"key1",
507- reservation_key=u"res1",
508- ramdisk_key=u"ram1",
509- kernel_key=u"kernel1",
510- launch_index=0,
511- image_key=u"image1",
512- account_name=None,
513- registration_password=None,
514- local_ipv4=u"10.0.0.1",
515- public_ipv4=u"10.0.0.2",
516- tags=None)
517- # The get_vm_info() needs to be deferred to the else. If vm-info is
518- # not specified in kwargs, get_vm_info() will typically be mocked.
519- if "vm_info" in kwargs:
520- message["vm-info"] = kwargs.pop("vm_info")
521- else:
522- message["vm-info"] = get_vm_info()
523- message.update(kwargs)
524- return message
525-
526- def test_cloud_registration(self):
527- """
528- When the 'cloud' configuration variable is set, cloud registration is
529- done instead of normal password-based registration. This means:
530-
531- - "Launch Data" is fetched from the EC2 Launch Data URL. This contains
532- a one-time password that is used during registration.
533- - A different "register-cloud-vm" message is sent to the server instead
534- of "register", containing the OTP. This message is handled by
535- immediately accepting the computer, instead of going through the
536- pending computer stage.
537- """
538- get_vm_info_mock = self.mocker.replace(get_vm_info)
539- get_vm_info_mock()
540- self.mocker.result("xen")
541- self.mocker.replay()
542- self.prepare_query_results()
543- self.prepare_cloud_registration(tags=u"server,london")
544-
545- # metadata is fetched and stored at reactor startup:
546- self.reactor.fire("run")
547-
548- # And the metadata returned determines the URLs that are used
549- self.assertEqual(self.transport.get_url(),
550- "https://example.com/message-system")
551- self.assertEqual(self.pinger.get_url(),
552- "http://example.com/ping")
553- # Lets make sure those values were written back to the config file
554- new_config = BrokerConfiguration()
555- new_config.load_configuration_file(self.config_filename)
556- self.assertEqual(new_config.url, "https://example.com/message-system")
557- self.assertEqual(new_config.ping_url, "http://example.com/ping")
558-
559- # Okay! Exchange should cause the registration to happen.
560- self.exchanger.exchange()
561- # This *should* be asynchronous, but I think a billion tests are
562- # written like this
563- self.assertEqual(len(self.transport.payloads), 1)
564- self.assertMessages(
565- self.transport.payloads[0]["messages"],
566- [self.get_expected_cloud_message(tags=u"server,london",
567- vm_info="xen")])
568-
569- def test_cloud_registration_with_access_group(self):
570- """
571- If the access_group field is presnet in the configuration, the
572- access_group field is present in the outgoing message for a VM
573- registration, and a notice appears in the logs.
574- """
575- self.prepare_query_results()
576- self.prepare_cloud_registration(access_group=u"dinosaurs",
577- tags=u"server,london")
578-
579- self.reactor.fire("run")
580- self.exchanger.exchange()
581- self.assertEqual(len(self.transport.payloads), 1)
582- self.assertMessages(
583- self.transport.payloads[0]["messages"],
584- [self.get_expected_cloud_message(
585- access_group=u"dinosaurs", tags=u"server,london")])
586-
587- def test_cloud_registration_with_otp(self):
588- """
589- If the OTP is present in the configuration, it's used to trigger the
590- registration instead of using the user data.
591- """
592- self.config.otp = "otp1"
593- self.prepare_query_results(user_data=None)
594-
595- self.prepare_cloud_registration()
596-
597- # metadata is fetched and stored at reactor startup:
598- self.reactor.fire("run")
599-
600- # Okay! Exchange should cause the registration to happen.
601- self.exchanger.exchange()
602- # This *should* be asynchronous, but I think a billion tests are
603- # written like this
604- self.assertEqual(len(self.transport.payloads), 1)
605- self.assertMessages(
606- self.transport.payloads[0]["messages"],
607- [self.get_expected_cloud_message()])
608-
609- def test_cloud_registration_with_invalid_tags(self):
610- """
611- Invalid tags in the configuration should result in the tags not being
612- sent to the server, and this fact logged.
613- """
614- self.log_helper.ignore_errors("Invalid tags provided for cloud "
615- "registration")
616- self.prepare_query_results()
617- self.prepare_cloud_registration(tags=u"<script>alert()</script>,hardy")
618-
619- # metadata is fetched and stored at reactor startup:
620- self.reactor.fire("run")
621- self.exchanger.exchange()
622- self.assertEqual(len(self.transport.payloads), 1)
623- self.assertMessages(self.transport.payloads[0]["messages"],
624- [self.get_expected_cloud_message(tags=None)])
625- self.assertEqual(self.logfile.getvalue().strip()[:-7],
626- "ERROR: Invalid tags provided for cloud "
627- "registration.\n "
628- "INFO: Queueing message to register with OTP\n "
629- "INFO: Sending registration message to exchange.\n "
630- " INFO: Starting message exchange with "
631- "https://example.com/message-system.\n "
632- "INFO: Message exchange completed in")
633-
634- def test_cloud_registration_with_ssl_ca_certificate(self):
635- """
636- If we have an SSL certificate CA included in the user-data, this should
637- be written out, and the configuration updated to reflect this.
638- """
639- key_filename = os.path.join(self.config.data_path,
640- "%s.ssl_public_key" % os.path.basename(self.config_filename))
641-
642- print_text_mock = self.mocker.replace(print_text)
643- print_text_mock("Writing SSL CA certificate to %s..." %
644- key_filename)
645- self.mocker.replay()
646- self.prepare_query_results(ssl_ca_certificate=u"1234567890")
647- self.prepare_cloud_registration(tags=u"server,london")
648- # metadata is fetched and stored at reactor startup:
649- self.reactor.fire("run")
650- # And the metadata returned determines the URLs that are used
651- self.assertEqual("https://example.com/message-system",
652- self.transport.get_url())
653- self.assertEqual(key_filename, self.transport._pubkey)
654- self.assertEqual("http://example.com/ping",
655- self.pinger.get_url())
656- # Let's make sure those values were written back to the config file
657- new_config = BrokerConfiguration()
658- new_config.load_configuration_file(self.config_filename)
659- self.assertEqual("https://example.com/message-system", new_config.url)
660- self.assertEqual("http://example.com/ping", new_config.ping_url)
661- self.assertEqual(key_filename, new_config.ssl_public_key)
662- self.assertEqual("1234567890", open(key_filename, "r").read())
663-
664- def test_wrong_user_data(self):
665- self.prepare_query_results(user_data="other stuff, not a bpickle")
666- self.prepare_cloud_registration()
667-
668- # Mock registration-failed call
669- reactor_mock = self.mocker.patch(self.reactor)
670- reactor_mock.fire("registration-failed")
671- self.mocker.replay()
672-
673- self.reactor.fire("run")
674- self.exchanger.exchange()
675-
676- def test_wrong_object_type_in_user_data(self):
677- self.prepare_query_results(user_data=True)
678- self.prepare_cloud_registration()
679-
680- # Mock registration-failed call
681- reactor_mock = self.mocker.patch(self.reactor)
682- reactor_mock.fire("registration-failed")
683- self.mocker.replay()
684-
685- self.reactor.fire("run")
686- self.exchanger.exchange()
687-
688- def test_user_data_with_not_enough_elements(self):
689- """
690- If the AMI launch index isn't represented in the list of OTPs in the
691- user data then BOOM.
692- """
693- self.prepare_query_results(launch_index=1)
694- self.prepare_cloud_registration()
695-
696- # Mock registration-failed call
697- reactor_mock = self.mocker.patch(self.reactor)
698- reactor_mock.fire("registration-failed")
699- self.mocker.replay()
700-
701- self.reactor.fire("run")
702- self.exchanger.exchange()
703-
704- def test_user_data_bpickle_without_otp(self):
705- self.prepare_query_results(user_data={"foo": "bar"})
706- self.prepare_cloud_registration()
707-
708- # Mock registration-failed call
709- reactor_mock = self.mocker.patch(self.reactor)
710- reactor_mock.fire("registration-failed")
711- self.mocker.replay()
712-
713- self.reactor.fire("run")
714- self.exchanger.exchange()
715-
716- def test_no_otp_fallback_to_account(self):
717- self.prepare_query_results(user_data="other stuff, not a bpickle",
718- instance_key=u"key1")
719- self.prepare_cloud_registration(account_name=u"onward",
720- registration_key=u"password",
721- tags=u"london,server")
722-
723- self.reactor.fire("run")
724- self.exchanger.exchange()
725-
726- self.assertEqual(len(self.transport.payloads), 1)
727- self.assertMessages(self.transport.payloads[0]["messages"],
728- [self.get_expected_cloud_message(
729- otp=None,
730- account_name=u"onward",
731- registration_password=u"password",
732- tags=u"london,server")])
733- self.assertEqual(self.logfile.getvalue().strip()[:-7],
734- "INFO: Queueing message to register with account u'onward' and "
735- "tags london,server as an EC2 instance.\n "
736- "INFO: Sending registration message to exchange.\n "
737- "INFO: Starting message exchange with http://localhost:91919.\n "
738- " INFO: Message exchange completed in")
739-
740- def test_queueing_cloud_registration_message_resets_message_store(self):
741- """
742- When a registration from a cloud is about to happen, the message store
743- is reset, because all previous messages are now meaningless.
744- """
745- self.mstore.set_accepted_types(list(self.mstore.get_accepted_types())
746- + ["test"])
747-
748- self.mstore.add({"type": "test"})
749-
750- self.prepare_query_results()
751-
752- self.prepare_cloud_registration()
753-
754- self.reactor.fire("run")
755- self.reactor.fire("pre-exchange")
756-
757- messages = self.mstore.get_pending_messages()
758- self.assertEqual(len(messages), 1)
759- self.assertEqual(messages[0]["type"], "register-cloud-vm")
760-
761- def test_cloud_registration_fetch_errors(self):
762- """
763- If fetching metadata fails, and we have no account details to fall
764- back to, we fire 'registration-failed'.
765- """
766- self.log_helper.ignore_errors(pycurl.error)
767-
768- def fetch_stub(url):
769- return fail(pycurl.error(7, "couldn't connect to host"))
770-
771- self.handler = RegistrationHandler(
772- self.config, self.identity, self.reactor, self.exchanger,
773- self.pinger, self.mstore, fetch_async=fetch_stub)
774-
775- self.fetch_stub = fetch_stub
776- self.prepare_query_results()
777- self.fetch_stub = fetch_stub
778-
779- self.prepare_cloud_registration()
780-
781- failed = []
782- self.reactor.call_on(
783- "registration-failed", lambda: failed.append(True))
784-
785- self.log_helper.ignore_errors("Got error while fetching meta-data")
786- self.reactor.fire("run")
787- self.exchanger.exchange()
788- self.assertEqual(failed, [True])
789- self.assertIn('error: (7, "couldn\'t connect to host")',
790- self.logfile.getvalue())
791-
792- def test_cloud_registration_continues_without_user_data(self):
793- """
794- If no user-data exists (i.e., the user-data URL returns a 404), then
795- register-cloud-vm still occurs.
796- """
797- self.log_helper.ignore_errors(HTTPCodeError)
798- self.prepare_query_results(user_data=HTTPCodeError(404, "ohno"))
799- self.prepare_cloud_registration(account_name="onward",
800- registration_key="password")
801-
802- self.reactor.fire("run")
803- self.exchanger.exchange()
804- self.assertIn("HTTPCodeError: Server returned HTTP code 404",
805- self.logfile.getvalue())
806- self.assertEqual(len(self.transport.payloads), 1)
807- self.assertMessages(self.transport.payloads[0]["messages"],
808- [self.get_expected_cloud_message(
809- otp=None,
810- account_name=u"onward",
811- registration_password=u"password")])
812-
813- def test_cloud_registration_continues_without_ramdisk(self):
814- """
815- If the instance doesn't have a ramdisk (ie, the query for ramdisk
816- returns a 404), then register-cloud-vm still occurs.
817- """
818- self.log_helper.ignore_errors(HTTPCodeError)
819- self.prepare_query_results(ramdisk_key=HTTPCodeError(404, "ohno"))
820- self.prepare_cloud_registration()
821-
822- self.reactor.fire("run")
823- self.exchanger.exchange()
824- self.assertIn("HTTPCodeError: Server returned HTTP code 404",
825- self.logfile.getvalue())
826- self.assertEqual(len(self.transport.payloads), 1)
827- self.assertMessages(self.transport.payloads[0]["messages"],
828- [self.get_expected_cloud_message(
829- ramdisk_key=None)])
830-
831- def test_cloud_registration_continues_without_kernel(self):
832- """
833- If the instance doesn't have a kernel (ie, the query for kernel
834- returns a 404), then register-cloud-vm still occurs.
835- """
836- self.log_helper.ignore_errors(HTTPCodeError)
837- self.prepare_query_results(kernel_key=HTTPCodeError(404, "ohno"))
838- self.prepare_cloud_registration()
839-
840- self.reactor.fire("run")
841- self.exchanger.exchange()
842- self.assertIn("HTTPCodeError: Server returned HTTP code 404",
843- self.logfile.getvalue())
844- self.assertEqual(len(self.transport.payloads), 1)
845- self.assertMessages(self.transport.payloads[0]["messages"],
846- [self.get_expected_cloud_message(
847- kernel_key=None)])
848-
849- def test_fall_back_to_normal_registration_when_metadata_fetch_fails(self):
850- """
851- If fetching metadata fails, but we do have an account name, then we
852- fall back to normal 'register' registration.
853- """
854- self.mstore.set_accepted_types(["register"])
855- self.log_helper.ignore_errors(HTTPCodeError)
856- self.prepare_query_results(
857- public_hostname=HTTPCodeError(404, "ohnoes"))
858- self.prepare_cloud_registration(account_name="onward",
859- registration_key="password")
860- self.config.computer_title = "whatever"
861- self.reactor.fire("run")
862- self.exchanger.exchange()
863- self.assertIn("HTTPCodeError: Server returned HTTP code 404",
864- self.logfile.getvalue())
865- self.assertEqual(len(self.transport.payloads), 1)
866- messages = self.transport.payloads[0]["messages"]
867- self.assertEqual("register", messages[0]["type"])
868-
869- def test_should_register_in_cloud(self):
870- """
871- The client should register when it's in the cloud even though
872- it doesn't have the normal account details.
873- """
874- self.mstore.set_accepted_types(self.mstore.get_accepted_types()
875- + ("register-cloud-vm",))
876- self.config.account_name = None
877- self.config.registration_key = None
878- self.config.computer_title = None
879- self.identity.secure_id = None
880- self.assertTrue(self.handler.should_register())
881-
882- def test_launch_index(self):
883- """
884- The client used the value in C{ami-launch-index} to choose the
885- appropriate OTP in the user data.
886- """
887- otp = "correct otp for launch index"
888- self.prepare_query_results(
889- user_data=self.get_user_data(otps=["wrong index", otp,
890- "wrong again"],),
891- instance_key="key1",
892- launch_index=1)
893-
894- self.prepare_cloud_registration()
895-
896- self.reactor.fire("run")
897- self.exchanger.exchange()
898- self.assertEqual(len(self.transport.payloads), 1)
899- self.assertMessages(self.transport.payloads[0]["messages"],
900- [self.get_expected_cloud_message(otp=otp,
901- launch_index=1)])
902-
903- def test_should_not_register_in_cloud(self):
904- """
905- Having a secure ID means we shouldn't register, even in the cloud.
906- """
907- self.mstore.set_accepted_types(self.mstore.get_accepted_types()
908- + ("register-cloud-vm",))
909- self.config.account_name = None
910- self.config.registration_key = None
911- self.config.computer_title = None
912- self.identity.secure_id = "hello"
913- self.assertFalse(self.handler.should_register())
914-
915- def test_should_not_register_without_register_cloud_vm(self):
916- """
917- If the server isn't accepting a 'register-cloud-vm' message,
918- we shouldn't register.
919- """
920- self.config.account_name = None
921- self.config.registration_key = None
922- self.config.computer_title = None
923- self.identity.secure_id = None
924- self.assertFalse(self.handler.should_register())
925-
926-
927-class IsCloudManagedTests(LandscapeTest):
928-
929- def setUp(self):
930- super(IsCloudManagedTests, self).setUp()
931- self.urls = []
932- self.responses = []
933-
934- def fake_fetch(self, url, connect_timeout=None):
935- self.urls.append((url, connect_timeout))
936- return self.responses.pop(0)
937-
938- def mock_socket(self):
939- """
940- Mock out socket usage by is_cloud_managed to wait for the network.
941- """
942- # Mock the socket.connect call that it also does
943- socket_class = self.mocker.replace("socket.socket", passthrough=False)
944- socket = socket_class()
945- socket.connect((EC2_HOST, 80))
946- socket.close()
947-
948- def test_is_managed(self):
949- """
950- L{is_cloud_managed} returns True if the EC2 user-data contains
951- Landscape instance information. It fetches the EC2 data with low
952- timeouts.
953- """
954- user_data = {"otps": ["otp1"], "exchange-url": "http://exchange",
955- "ping-url": "http://ping"}
956- self.responses = [dumps(user_data), "0"]
957-
958- self.mock_socket()
959- self.mocker.replay()
960-
961- self.assertTrue(is_cloud_managed(self.fake_fetch))
962- self.assertEqual(
963- self.urls,
964- [(EC2_API + "/user-data", 5),
965- (EC2_API + "/meta-data/ami-launch-index", 5)])
966-
967- def test_is_managed_index(self):
968- user_data = {"otps": ["otp1", "otp2"],
969- "exchange-url": "http://exchange",
970- "ping-url": "http://ping"}
971- self.responses = [dumps(user_data), "1"]
972- self.mock_socket()
973- self.mocker.replay()
974- self.assertTrue(is_cloud_managed(self.fake_fetch))
975-
976- def test_is_managed_wrong_index(self):
977- user_data = {"otps": ["otp1"], "exchange-url": "http://exchange",
978- "ping-url": "http://ping"}
979- self.responses = [dumps(user_data), "1"]
980- self.mock_socket()
981- self.mocker.replay()
982- self.assertFalse(is_cloud_managed(self.fake_fetch))
983-
984- def test_is_managed_exchange_url(self):
985- user_data = {"otps": ["otp1"], "ping-url": "http://ping"}
986- self.responses = [dumps(user_data), "0"]
987- self.mock_socket()
988- self.mocker.replay()
989- self.assertFalse(is_cloud_managed(self.fake_fetch))
990-
991- def test_is_managed_ping_url(self):
992- user_data = {"otps": ["otp1"], "exchange-url": "http://exchange"}
993- self.responses = [dumps(user_data), "0"]
994- self.mock_socket()
995- self.mocker.replay()
996- self.assertFalse(is_cloud_managed(self.fake_fetch))
997-
998- def test_is_managed_bpickle(self):
999- self.responses = ["some other user data", "0"]
1000- self.mock_socket()
1001- self.mocker.replay()
1002- self.assertFalse(is_cloud_managed(self.fake_fetch))
1003-
1004- def test_is_managed_no_data(self):
1005- self.responses = ["", "0"]
1006- self.mock_socket()
1007- self.mocker.replay()
1008- self.assertFalse(is_cloud_managed(self.fake_fetch))
1009-
1010- def test_is_managed_fetch_not_found(self):
1011-
1012- def fake_fetch(url, connect_timeout=None):
1013- raise HTTPCodeError(404, "ohnoes")
1014-
1015- self.mock_socket()
1016- self.mocker.replay()
1017- self.assertFalse(is_cloud_managed(fake_fetch))
1018-
1019- def test_is_managed_fetch_error(self):
1020-
1021- def fake_fetch(url, connect_timeout=None):
1022- raise FetchError(7, "couldn't connect to host")
1023-
1024- self.mock_socket()
1025- self.mocker.replay()
1026- self.assertFalse(is_cloud_managed(fake_fetch))
1027-
1028- def test_waits_for_network(self):
1029- """
1030- is_cloud_managed will wait until the network before trying to fetch
1031- the EC2 user data.
1032- """
1033- user_data = {"otps": ["otp1"], "exchange-url": "http://exchange",
1034- "ping-url": "http://ping"}
1035- self.responses = [dumps(user_data), "0"]
1036-
1037- self.mocker.order()
1038- time_sleep = self.mocker.replace("time.sleep", passthrough=False)
1039- socket_class = self.mocker.replace("socket.socket", passthrough=False)
1040- socket_obj = socket_class()
1041- socket_obj.connect((EC2_HOST, 80))
1042- self.mocker.throw(socket.error("woops"))
1043- time_sleep(1)
1044- socket_obj = socket_class()
1045- socket_obj.connect((EC2_HOST, 80))
1046- self.mocker.result(None)
1047- socket_obj.close()
1048- self.mocker.replay()
1049- self.assertTrue(is_cloud_managed(self.fake_fetch))
1050-
1051- def test_waiting_times_out(self):
1052- """
1053- We'll only wait five minutes for the network to come up.
1054- """
1055-
1056- def fake_fetch(url, connect_timeout=None):
1057- raise FetchError(7, "couldn't connect to host")
1058-
1059- self.mocker.order()
1060- time_sleep = self.mocker.replace("time.sleep", passthrough=False)
1061- time_time = self.mocker.replace("time.time", passthrough=False)
1062- time_time()
1063- self.mocker.result(100)
1064- socket_class = self.mocker.replace("socket.socket", passthrough=False)
1065- socket_obj = socket_class()
1066- socket_obj.connect((EC2_HOST, 80))
1067- self.mocker.throw(socket.error("woops"))
1068- time_sleep(1)
1069- time_time()
1070- self.mocker.result(401)
1071- self.mocker.replay()
1072- # Mocking time.time is dangerous, because the test harness calls it. So
1073- # we explicitly reset mocker before returning from the test.
1074- try:
1075- self.assertFalse(is_cloud_managed(fake_fetch))
1076- finally:
1077- self.mocker.reset()
1078-
1079-
1080 class ProvisioningRegistrationTest(RegistrationHandlerTestBase):
1081
1082 def test_provisioned_machine_registration_with_otp(self):
1083
1084=== modified file 'landscape/configuration.py'
1085--- landscape/configuration.py 2014-07-01 14:52:02 +0000
1086+++ landscape/configuration.py 2014-07-17 06:52:14 +0000
1087@@ -581,7 +581,7 @@
1088 decode_base64_ssl_public_certificate(config)
1089 config.write()
1090 # Restart the client to ensure that it's using the new configuration.
1091- if not config.no_start and not config.otp:
1092+ if not config.no_start:
1093 try:
1094 sysvconfig.restart_landscape()
1095 except ProcessError:
1096
1097=== modified file 'landscape/message_schemas.py'
1098--- landscape/message_schemas.py 2014-06-09 08:18:05 +0000
1099+++ landscape/message_schemas.py 2014-07-17 06:52:14 +0000
1100@@ -211,6 +211,9 @@
1101 {"otp": Bytes()})
1102
1103
1104+# XXX The register-cloud-vm message is obsolete, it's kept around just to not
1105+# break older LDS releases that import it (the last LDS release to have it
1106+# is 14.07). Eventually it shall be dropped.
1107 REGISTER_CLOUD_VM = Message(
1108 "register-cloud-vm",
1109 {"hostname": Unicode(),
1110
1111=== modified file 'landscape/tests/test_configuration.py'
1112--- landscape/tests/test_configuration.py 2014-02-25 18:06:48 +0000
1113+++ landscape/tests/test_configuration.py 2014-07-17 06:52:14 +0000
1114@@ -820,7 +820,6 @@
1115 "--ping-interval", "30",
1116 "--http-proxy", "",
1117 "--https-proxy", "",
1118- "--otp", "",
1119 "--tags", "",
1120 "--provisioning-otp", ""]
1121 config = self.get_config(args)
1122@@ -837,7 +836,6 @@
1123 "https_proxy = \n"
1124 "url = https://landscape.canonical.com/message-system\n"
1125 "exchange_interval = 900\n"
1126- "otp = \n"
1127 "ping_interval = 30\n"
1128 "ping_url = http://landscape.canonical.com/ping\n"
1129 "provisioning_otp = \n"
1130@@ -862,20 +860,6 @@
1131 config = self.get_config(["--silent", "-t", "rex"])
1132 self.assertRaises(ConfigurationError, setup, config)
1133
1134- def test_silent_setup_with_otp(self):
1135- """
1136- If the OTP is specified, there is no need to pass the account name and
1137- the computer title.
1138- """
1139- sysvconfig_mock = self.mocker.patch(SysVConfig)
1140- sysvconfig_mock.set_start_on_boot(True)
1141- self.mocker.replay()
1142-
1143- config = self.get_config(["--silent", "--otp", "otp1"])
1144- setup(config)
1145-
1146- self.assertEqual("otp1", config.otp)
1147-
1148 def test_silent_setup_with_provisioning_otp(self):
1149 """
1150 If the provisioning OTP is specified, there is no need to pass the
1151
1152=== removed file 'scripts/landscape-is-cloud-managed'
1153--- scripts/landscape-is-cloud-managed 2009-04-09 14:32:45 +0000
1154+++ scripts/landscape-is-cloud-managed 1970-01-01 00:00:00 +0000
1155@@ -1,12 +0,0 @@
1156-#!/usr/bin/python
1157-import sys, os
1158-if os.path.dirname(os.path.abspath(sys.argv[0])) == os.path.abspath("scripts"):
1159- sys.path.insert(0, "./")
1160-else:
1161- from landscape.lib.warning import hide_warnings
1162- hide_warnings()
1163-
1164-from landscape.broker.registration import is_cloud_managed
1165-
1166-# We return 0 if it succeeds
1167-sys.exit(not is_cloud_managed())
1168
1169=== modified file 'setup.py'
1170--- setup.py 2013-06-03 12:26:30 +0000
1171+++ setup.py 2014-07-17 06:52:14 +0000
1172@@ -56,7 +56,6 @@
1173 "scripts/landscape-package-reporter",
1174 "scripts/landscape-release-upgrader",
1175 "scripts/landscape-sysinfo",
1176- "scripts/landscape-is-cloud-managed",
1177 "scripts/landscape-dbus-proxy",
1178 "scripts/landscape-client-settings-mechanism",
1179 "scripts/landscape-client-registration-mechanism",

Subscribers

People subscribed via source and target branches

to all changes: