Merge lp:~free.ekanayaka/landscape-client/drop-cloud-registration into lp:~free.ekanayaka/landscape-client/drop-old-juju-info

Proposed by Free Ekanayaka
Status: Superseded
Proposed branch: lp:~free.ekanayaka/landscape-client/drop-cloud-registration
Merge into: lp:~free.ekanayaka/landscape-client/drop-old-juju-info
Diff against target: 1241 lines (+40/-942)
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 (+6/-210)
landscape/broker/tests/test_registration.py (+20/-669)
landscape/configuration.py (+1/-1)
landscape/message_schemas.py (+11/-1)
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
Landscape Pending
Landscape Pending
Review via email: mp+226992@code.launchpad.net

Description of the change

Drop the cloud registration code and associated tests.

To post a comment you must log in.
783. By Free Ekanayaka

Address review comments

Unmerged revisions

783. By Free Ekanayaka

Address review comments

782. By Free Ekanayaka

Add comment

781. By Free Ekanayaka

Revert drop-juju-info

780. By Free Ekanayaka

Merge from drop-old-juju-info

779. By Free Ekanayaka

Merge from drop-old-juju-info

778. By Free Ekanayaka

Drop cloud registration

Preview Diff

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

Subscribers

People subscribed via source and target branches

to all changes: