Merge lp:~adam-collard/landscape-client/juju-info-at-registration into lp:~landscape/landscape-client/trunk

Proposed by Adam Collard
Status: Merged
Approved by: Adam Collard
Approved revision: no longer in the source branch.
Merged at revision: 733
Proposed branch: lp:~adam-collard/landscape-client/juju-info-at-registration
Merge into: lp:~landscape/landscape-client/trunk
Diff against target: 614 lines (+240/-118)
13 files modified
landscape/broker/registration.py (+69/-51)
landscape/broker/tests/helpers.py (+3/-0)
landscape/broker/tests/test_registration.py (+38/-7)
landscape/deployment.py (+5/-0)
landscape/lib/juju.py (+26/-0)
landscape/lib/tests/test_encoding.py (+1/-1)
landscape/lib/tests/test_juju.py (+62/-0)
landscape/message_schemas.py (+10/-4)
landscape/monitor/config.py (+0/-7)
landscape/monitor/jujuinfo.py (+4/-22)
landscape/monitor/tests/test_config.py (+0/-11)
landscape/monitor/tests/test_jujuinfo.py (+9/-12)
landscape/tests/test_deployment.py (+13/-3)
To merge this branch: bzr merge lp:~adam-collard/landscape-client/juju-info-at-registration
Reviewer Review Type Date Requested Status
Björn Tillenius (community) Approve
Alberto Donato (community) Approve
Review via email: mp+187836@code.launchpad.net

Commit message

Add Juju information at registration time.

 * Changed schema of JUJU_INFO message to be strict about the keys
 * Re-used schema for REGISTER and REGISTER_CLOUD_VM messages
 * Added l.lib.juju as resting ground for json parsing and error handling
 * Moved juju_filename to main config object, not just specific to monitor

Description of the change

Add Juju information at registration time.

 * Changed schema of JUJU_INFO message to be strict about the keys
 * Re-used schema for REGISTER and REGISTER_CLOUD_VM messages
 * Added l.lib.juju as resting ground for json parsing and error handling
 * Moved juju_filename to main config object, not just specific to monitor

To post a comment you must log in.
Revision history for this message
Alberto Donato (ack) wrote :

+1, looks good!

#1:
+ juju_registration_info = {}
+ juju_registration_info["environment-uuid"] = juju_info[
+ "environment-uuid"]
+ juju_registration_info["api-addresses"] = juju_info[
+ "api-addresses"].split()
+ juju_registration_info["unit-name"] = juju_info["unit-name"]

I think the juju_info["api-addresses"].split() should me moved inside get_juju_info(), since there are currently two places where the split is performed after getting the info.
As a result this could we written as

+ juju_registration_info = dict(
+ (key, value) for key, value in juju_info
+ if key in ["environment-uuid", "api-addresses", "unit-name"])

#2:
+import os.path

Please use "from os import path" instead.

#3:
+class JujuTest(LandscapeTest):

Tests in this class don't have docstrings.

#4:
+ self.assertEqual("DEAD-BEEF", juju_info["environment-uuid"])
+ self.assertEqual("juju-unit-name", juju_info["unit-name"])
+ self.assertEqual("10.0.3.1:17070", juju_info["api-addresses"])
+ self.assertEqual("127.0.0.1", juju_info["private-address"])

It'd be better to compare juju_info with an expected dict, to ensure there are no extra keys.

review: Approve
Revision history for this message
Björn Tillenius (bjornt) wrote :

Looks good, nothing major, but still marking it as Needs Fixing,
since [6] might change the diff a bit.

[1]

29 + self._juju_data = self._get_juju_data()

I think it would be cleaner to collect the data when on "run", just
like ec2 data is collected.

[2]

222 + self.assertMessages(self.mstore.get_pending_messages(),
223 + [{"type": "register",
224 + "computer_title": self.config.computer_title,
225 + "account_name": "account_name",
226 + "registration_password": None,
227 + "hostname": socket.getfqdn(),
228 + "vm-info": get_vm_info(),
229 + "tags": None,
230 + "juju-info": {
231 + "environment-uuid": "DEAD-BEEF",
232 + "api-addresses": ["10.0.3.1:17070"],
233 + "unit-name": "juju-unit-name"}
234 + }])

The indentation looks off here. Also, please use valid unit names,
like service/0.

[3]
264 - "tags": None}])
265 + "tags": None,
266 + "juju-info": {
267 + "environment-uuid": "DEAD-BEEF",
268 + "api-addresses": ["10.0.3.1:17070"],
269 + "unit-name": "juju-unit-name"}}])

Indentation is off here as well.

[4]
365 + def test_get_juju_info_sample_data(self):
366 + """L{get_juju_info} works with sample data."""

What does "works with sample data" mean? It's implied that our
code works. Better to explain what the code is expected to do instead.

[5]
384 + self.assertTrue(
385 + "Error attempting to read JSON" in self.logfile.getvalue())

Please use assertIn instead, which gives a better error message on failures.

[6]

435 + "juju-info": KeyDict(juju_data)},
436 + optional=["tags", "vm-info", "public_ipv4", "local_ipv4",
437 + "juju-info"])

I think we shouldn't include juju-info in the register-cloud-vm message.
By default Juju instances won't use that the register-cloud-vm message,
and the plan is to remove it altogether.

review: Needs Fixing
Revision history for this message
Adam Collard (adam-collard) wrote :

On 27 September 2013 15:32, Björn Tillenius <email address hidden> wrote:

> [1]
>
> 29 + self._juju_data = self._get_juju_data()
>
> I think it would be cleaner to collect the data when on "run", just
> like ec2 data is collected.
>

OK. Done!

>
> [2]
>
>
> 222 + self.assertMessages(self.mstore.get_pending_messages(),
> 223 + [{"type": "register",
> 224 + "computer_title":
> self.config.computer_title,
> 225 + "account_name": "account_name",
> 226 + "registration_password": None,
> 227 + "hostname": socket.getfqdn(),
> 228 + "vm-info": get_vm_info(),
> 229 + "tags": None,
> 230 + "juju-info": {
> 231 + "environment-uuid": "DEAD-BEEF",
> 232 + "api-addresses": ["10.0.3.1:17070"],
> 233 + "unit-name": "juju-unit-name"}
> 234 + }])
>
> The indentation looks off here. Also, please use valid unit names,
> like service/0.
>

Emacs wanted to indent it like that - have shifted things around a bit.

Done!

>
>
> [3]
> 264 - "tags": None}])
> 265 + "tags": None,
> 266 + "juju-info": {
> 267 + "environment-uuid": "DEAD-BEEF",
> 268 + "api-addresses": ["10.0.3.1:17070
> "],
> 269 + "unit-name": "juju-unit-name"}}])
>
> Indentation is off here as well.
>

This became irrelevant because of [6]

>
> [4]
> 365 + def test_get_juju_info_sample_data(self):
> 366 + """L{get_juju_info} works with sample data."""
>
> What does "works with sample data" mean? It's implied that our
> code works. Better to explain what the code is expected to do instead.
>

I've tried re-phrasing it. It really is just "does it work with realistic
data"

>
> [5]
> 384 + self.assertTrue(
> 385 + "Error attempting to read JSON" in
> self.logfile.getvalue())
>
> Please use assertIn instead, which gives a better error message on
> failures.
>

Argh. I thought we didn't have it because client has to work on 2.6, but
mocker implements it. Grr, I had it as assertIn and the other test using
assertIs then carefully undid them. Thanks :)

>
>
> [6]
>
> 435 + "juju-info": KeyDict(juju_data)},
> 436 + optional=["tags", "vm-info", "public_ipv4", "local_ipv4",
> 437 + "juju-info"])
>
> I think we shouldn't include juju-info in the register-cloud-vm message.
> By default Juju instances won't use that the register-cloud-vm message,
> and the plan is to remove it altogether.
>

OK, I wasn't sure what this was so erred on the side of caution and
included it. Removed.

Revision history for this message
Björn Tillenius (bjornt) wrote :

Thanks, looks good now, +1!

[7]

358 + def test_get_juju_info_sample_data(self):
359 + """L{get_juju_info} parses realistic data properly."""

I might be nitpicking a bit :), but I'd say never use words like
"works", "properly", "as expected", etc. in test docstrings. How about?

  """ L{get_juju_info} parses JSON data from the juju_filename file."""

[8]

I don't see a test for when get_juju_info parses a file having
multiple API endpoints. Is there one?

review: Approve
732. By Björn Tillenius

Merge unmet-dependencies-bug-1203855 [f=1203855] [r=tribaal,ack] [a=Björn Tillenius]
Don't omit the package from the package changer result text if it's
broken but we don't know why.

When a package is broken we check certain dependency to see whether they
are satisfied. If all the dependencies we check are satisified, the
package wasn't reported as being broken.

Ideally we should write why the package is broken, but I have been
unable to reproduce the error in the bug report.

733. By Adam Collard

Merge juju-info-at-registration [f=1229630] [r=bjornt,ack] [a=Adam Collard]
Add Juju information at registration time.

 * Changed schema of JUJU_INFO message to be strict about the keys
 * Re-used schema for REGISTER and REGISTER_CLOUD_VM messages
 * Added l.lib.juju as resting ground for json parsing and error handling
 * Moved juju_filename to main config object, not just specific to monitor

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'landscape/broker/registration.py'
--- landscape/broker/registration.py 2013-07-09 15:58:36 +0000
+++ landscape/broker/registration.py 2013-09-27 14:18:49 +0000
@@ -5,7 +5,7 @@
5as a new client without providing any identification credentials, and the5as a new client without providing any identification credentials, and the
6server replies with the available registration mechanisms. At this point6server replies with the available registration mechanisms. At this point
7the machinery in this module will notice that we have no identification7the machinery in this module will notice that we have no identification
8credentials yet and that the server accepts regiration 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 time11import time
@@ -14,8 +14,11 @@
1414
15from twisted.internet.defer import Deferred15from twisted.internet.defer import Deferred
1616
17from landscape.message_schemas import juju_data
18
17from landscape.lib.bpickle import loads19from landscape.lib.bpickle import loads
18from landscape.lib.log import log_failure20from landscape.lib.log import log_failure
21from landscape.lib.juju import get_juju_info
19from landscape.lib.fetch import fetch, FetchError22from landscape.lib.fetch import fetch, FetchError
20from landscape.lib.tag import is_valid_tag_list23from landscape.lib.tag import is_valid_tag_list
21from landscape.lib.network import get_fqdn24from landscape.lib.network import get_fqdn
@@ -97,6 +100,7 @@
97 self._pinger = pinger100 self._pinger = pinger
98 self._message_store = message_store101 self._message_store = message_store
99 self._reactor.call_on("run", self._fetch_ec2_data)102 self._reactor.call_on("run", self._fetch_ec2_data)
103 self._reactor.call_on("run", self._get_juju_data)
100 self._reactor.call_on("pre-exchange", self._handle_pre_exchange)104 self._reactor.call_on("pre-exchange", self._handle_pre_exchange)
101 self._reactor.call_on("exchange-done", self._handle_exchange_done)105 self._reactor.call_on("exchange-done", self._handle_exchange_done)
102 self._exchange.register_message("set-id", self._handle_set_id)106 self._exchange.register_message("set-id", self._handle_set_id)
@@ -107,6 +111,7 @@
107 self._fetch_async = fetch_async111 self._fetch_async = fetch_async
108 self._otp = None112 self._otp = None
109 self._ec2_data = None113 self._ec2_data = None
114 self._juju_data = None
110115
111 def should_register(self):116 def should_register(self):
112 id = self._identity117 id = self._identity
@@ -134,6 +139,14 @@
134 self._exchange.exchange()139 self._exchange.exchange()
135 return result140 return result
136141
142 def _get_juju_data(self):
143 """Load Juju information."""
144 juju_info = get_juju_info(self._config)
145 if juju_info is None:
146 return None
147 self._juju_data = dict(
148 (key, juju_info[key]) for key in juju_data)
149
137 def _get_data(self, path, accumulate):150 def _get_data(self, path, accumulate):
138 """151 """
139 Get data at C{path} on the EC2 API endpoint, and add the result to the152 Get data at C{path} on the EC2 API endpoint, and add the result to the
@@ -254,65 +267,70 @@
254 # registration.267 # registration.
255 self._should_register = self.should_register()268 self._should_register = self.should_register()
256269
257 if self._should_register:270 if not self._should_register:
258 id = self._identity271 return
259 self._message_store.delete_all_messages()272 id = self._identity
260 tags = id.tags273 self._message_store.delete_all_messages()
261 if not is_valid_tag_list(tags):274 tags = id.tags
262 tags = None275 if not is_valid_tag_list(tags):
263 logging.error("Invalid tags provided for cloud "276 tags = None
264 "registration.")277 logging.error("Invalid tags provided for cloud registration.")
265 if self._config.cloud and self._ec2_data is not None:278 if self._config.cloud and self._ec2_data is not None:
266 if self._otp:279 if self._otp:
267 logging.info("Queueing message to register with OTP")280 logging.info("Queueing message to register with OTP")
268 message = {"type": "register-cloud-vm",281 message = {"type": "register-cloud-vm",
269 "otp": self._otp,282 "otp": self._otp,
270 "hostname": get_fqdn(),283 "hostname": get_fqdn(),
271 "account_name": None,284 "account_name": None,
272 "registration_password": None,285 "registration_password": None,
273 "tags": tags,286 "tags": tags,
274 "vm-info": get_vm_info()}287 "vm-info": get_vm_info()}
275 message.update(self._ec2_data)288 message.update(self._ec2_data)
276 self._exchange.send(message)289 if self._juju_data is not None:
277 elif id.account_name:290 message["juju-info"] = self._juju_data
278 with_tags = ["", u"and tags %s " % tags][bool(tags)]291 self._exchange.send(message)
279 logging.info(
280 u"Queueing message to register with account %r %s"
281 u"as an EC2 instance." % (id.account_name, with_tags))
282 message = {"type": "register-cloud-vm",
283 "otp": None,
284 "hostname": get_fqdn(),
285 "account_name": id.account_name,
286 "registration_password": \
287 id.registration_key,
288 "tags": tags,
289 "vm-info": get_vm_info()}
290 message.update(self._ec2_data)
291 self._exchange.send(message)
292 else:
293 self._reactor.fire("registration-failed")
294 elif id.account_name:292 elif id.account_name:
295 with_word = ["without", "with"][bool(id.registration_key)]
296 with_tags = ["", u"and tags %s " % tags][bool(tags)]293 with_tags = ["", u"and tags %s " % tags][bool(tags)]
297 logging.info(u"Queueing message to register with account %r %s"294 logging.info(
298 "%s a password." % (id.account_name, with_tags,295 u"Queueing message to register with account %r %s"
299 with_word))296 u"as an EC2 instance." % (id.account_name, with_tags))
300 message = {"type": "register",297 message = {"type": "register-cloud-vm",
301 "computer_title": id.computer_title,298 "otp": None,
299 "hostname": get_fqdn(),
302 "account_name": id.account_name,300 "account_name": id.account_name,
303 "registration_password": id.registration_key,301 "registration_password": id.registration_key,
304 "hostname": get_fqdn(),
305 "tags": tags,302 "tags": tags,
306 "vm-info": get_vm_info()}303 "vm-info": get_vm_info()}
307 self._exchange.send(message)304 message.update(self._ec2_data)
308 elif self._config.provisioning_otp:305 if self._juju_data is not None:
309 logging.info(u"Queueing message to register with OTP as a"306 message["juju-info"] = self._juju_data
310 u" newly provisioned machine.")
311 message = {"type": "register-provisioned-machine",
312 "otp": self._config.provisioning_otp}
313 self._exchange.send(message)307 self._exchange.send(message)
314 else:308 else:
315 self._reactor.fire("registration-failed")309 self._reactor.fire("registration-failed")
310 elif id.account_name:
311 with_word = ["without", "with"][bool(id.registration_key)]
312 with_tags = ["", u"and tags %s " % tags][bool(tags)]
313 logging.info(u"Queueing message to register with account %r %s"
314 "%s a password." % (id.account_name, with_tags,
315 with_word))
316 message = {"type": "register",
317 "computer_title": id.computer_title,
318 "account_name": id.account_name,
319 "registration_password": id.registration_key,
320 "hostname": get_fqdn(),
321 "tags": tags,
322 "vm-info": get_vm_info()}
323 if self._juju_data is not None:
324 message["juju-info"] = self._juju_data
325 self._exchange.send(message)
326 elif self._config.provisioning_otp:
327 logging.info(u"Queueing message to register with OTP as a"
328 u" newly provisioned machine.")
329 message = {"type": "register-provisioned-machine",
330 "otp": self._config.provisioning_otp}
331 self._exchange.send(message)
332 else:
333 self._reactor.fire("registration-failed")
316334
317 def _handle_set_id(self, message):335 def _handle_set_id(self, message):
318 """Registered handler for the C{"set-id"} event.336 """Registered handler for the C{"set-id"} event.
319337
=== modified file 'landscape/broker/tests/helpers.py'
--- landscape/broker/tests/helpers.py 2013-05-07 22:47:06 +0000
+++ landscape/broker/tests/helpers.py 2013-09-27 14:18:49 +0000
@@ -127,6 +127,9 @@
127127
128 test_case.fetch_func = fetch_async128 test_case.fetch_func = fetch_async
129 test_case.config.cloud = getattr(test_case, "cloud", False)129 test_case.config.cloud = getattr(test_case, "cloud", False)
130 if hasattr(test_case, "juju_contents"):
131 test_case.makeFile(
132 test_case.juju_contents, path=test_case.config.juju_filename)
130 test_case.handler = RegistrationHandler(133 test_case.handler = RegistrationHandler(
131 test_case.config, test_case.identity, test_case.reactor,134 test_case.config, test_case.identity, test_case.reactor,
132 test_case.exchanger, test_case.pinger, test_case.mstore,135 test_case.exchanger, test_case.pinger, test_case.mstore,
133136
=== modified file 'landscape/broker/tests/test_registration.py'
--- landscape/broker/tests/test_registration.py 2013-08-20 23:48:23 +0000
+++ landscape/broker/tests/test_registration.py 2013-09-27 14:18:49 +0000
@@ -1,3 +1,4 @@
1import json
1import os2import os
2import logging3import logging
3import pycurl4import pycurl
@@ -177,7 +178,7 @@
177 """178 """
178 When a computer_title and account_name are available, no179 When a computer_title and account_name are available, no
179 secure_id is set, and an exchange is about to happen,180 secure_id is set, and an exchange is about to happen,
180 queue a registration message.181 queue a registration message with VM information.
181 """182 """
182 get_vm_info_mock = self.mocker.replace(get_vm_info)183 get_vm_info_mock = self.mocker.replace(get_vm_info)
183 get_vm_info_mock()184 get_vm_info_mock()
@@ -484,6 +485,38 @@
484 "tags": None}])485 "tags": None}])
485486
486487
488class JujuRegistrationHandlerTest(RegistrationHandlerTestBase):
489
490 juju_contents = json.dumps({"environment-uuid": "DEAD-BEEF",
491 "unit-name": "service/0",
492 "api-addresses": "10.0.3.1:17070",
493 "private-address": "127.0.0.1"})
494
495 def test_juju_information_added_when_present(self):
496 """
497 When Juju information is found in $data_dir/juju-info.json,
498 key parts of it are sent in the registration message.
499 """
500 self.mstore.set_accepted_types(["register"])
501 self.config.account_name = "account_name"
502 self.reactor.fire("run")
503 self.reactor.fire("pre-exchange")
504
505 self.assertMessages(
506 self.mstore.get_pending_messages(),
507 [{"type": "register",
508 "computer_title": self.config.computer_title,
509 "account_name": "account_name",
510 "registration_password": None,
511 "hostname": socket.getfqdn(),
512 "vm-info": get_vm_info(),
513 "tags": None,
514 "juju-info": {"environment-uuid": "DEAD-BEEF",
515 "api-addresses": ["10.0.3.1:17070"],
516 "unit-name": "service/0"}
517 }])
518
519
487class CloudRegistrationHandlerTest(RegistrationHandlerTestBase):520class CloudRegistrationHandlerTest(RegistrationHandlerTestBase):
488521
489 cloud = True522 cloud = True
@@ -574,7 +607,6 @@
574 local_ipv4=u"10.0.0.1",607 local_ipv4=u"10.0.0.1",
575 public_ipv4=u"10.0.0.2",608 public_ipv4=u"10.0.0.2",
576 tags=None)609 tags=None)
577
578 # The get_vm_info() needs to be deferred to the else. If vm-info is610 # The get_vm_info() needs to be deferred to the else. If vm-info is
579 # specified in kwargs, get_vm_info() will typically be mocked.611 # specified in kwargs, get_vm_info() will typically be mocked.
580 if "vm_info" in kwargs:612 if "vm_info" in kwargs:
@@ -912,7 +944,8 @@
912 "registration_password": u"password",944 "registration_password": u"password",
913 "hostname": socket.getfqdn(),945 "hostname": socket.getfqdn(),
914 "vm-info": get_vm_info(),946 "vm-info": get_vm_info(),
915 "tags": None}])947 "tags": None,
948 }])
916949
917 def test_should_register_in_cloud(self):950 def test_should_register_in_cloud(self):
918 """951 """
@@ -1157,7 +1190,5 @@
1157 self.config.provisioning_otp = ""1190 self.config.provisioning_otp = ""
1158 self.reactor.fire("pre-exchange")1191 self.reactor.fire("pre-exchange")
11591192
1160 self.assertMessages([],1193 self.assertMessages([], self.mstore.get_pending_messages())
1161 self.mstore.get_pending_messages())1194 self.assertEqual(u"", self.logfile.getvalue().strip())
1162 self.assertEqual(u"",
1163 self.logfile.getvalue().strip())
11641195
=== modified file 'landscape/deployment.py'
--- landscape/deployment.py 2013-09-25 08:50:25 +0000
+++ landscape/deployment.py 2013-09-27 14:18:49 +0000
@@ -419,6 +419,11 @@
419 """419 """
420 return os.path.join(self.data_path, "annotations.d")420 return os.path.join(self.data_path, "annotations.d")
421421
422 @property
423 def juju_filename(self):
424 """Get the path to the Juju JSON file."""
425 return os.path.join(self.data_path, "juju-info.json")
426
422427
423def get_versioned_persist(service):428def get_versioned_persist(service):
424 """Get a L{Persist} database with upgrade rules applied.429 """Get a L{Persist} database with upgrade rules applied.
425430
=== added file 'landscape/lib/juju.py'
--- landscape/lib/juju.py 1970-01-01 00:00:00 +0000
+++ landscape/lib/juju.py 2013-09-27 14:18:49 +0000
@@ -0,0 +1,26 @@
1import json
2import logging
3import os.path
4
5from landscape.lib.fs import read_file
6
7
8def get_juju_info(config):
9 """
10 Returns the Juju info or C{None} if the path referenced from
11 L{config} is not a file or that file isn't valid JSON.
12 """
13 juju_filename = config.juju_filename
14 if not os.path.isfile(juju_filename):
15 return None
16 json_contents = read_file(juju_filename)
17 try:
18 juju_info = json.loads(json_contents)
19 except Exception:
20 logging.exception(
21 "Error attempting to read JSON from %s" % juju_filename)
22 return None
23 else:
24 if "api-addresses" in juju_info:
25 juju_info["api-addresses"] = juju_info["api-addresses"].split()
26 return juju_info
027
=== modified file 'landscape/lib/tests/test_encoding.py'
--- landscape/lib/tests/test_encoding.py 2013-03-20 08:58:22 +0000
+++ landscape/lib/tests/test_encoding.py 2013-09-27 14:18:49 +0000
@@ -4,7 +4,7 @@
4from landscape.lib.encoding import encode_if_needed, encode_dict_if_needed4from landscape.lib.encoding import encode_if_needed, encode_dict_if_needed
55
66
7class EncodingTestTest(LandscapeTest):7class EncodingTest(LandscapeTest):
88
9 def test_encode_if_needed_utf_string(self):9 def test_encode_if_needed_utf_string(self):
10 """10 """
1111
=== added file 'landscape/lib/tests/test_juju.py'
--- landscape/lib/tests/test_juju.py 1970-01-01 00:00:00 +0000
+++ landscape/lib/tests/test_juju.py 2013-09-27 14:18:49 +0000
@@ -0,0 +1,62 @@
1from collections import namedtuple
2import json
3
4from landscape.tests.helpers import LandscapeTest
5from landscape.lib.juju import get_juju_info
6
7
8SAMPLE_JUJU_INFO = json.dumps({"environment-uuid": "DEAD-BEEF",
9 "unit-name": "service/0",
10 "api-addresses": "10.0.3.1:17070",
11 "private-address": "127.0.0.1"})
12
13
14class JujuTest(LandscapeTest):
15
16 Config = namedtuple("Config", "juju_filename")
17
18 def test_get_juju_info_sample_data(self):
19 """L{get_juju_info} parses JSON data from the juju_filename file."""
20 stub_config = self.Config(self.makeFile(SAMPLE_JUJU_INFO))
21 juju_info = get_juju_info(stub_config)
22 self.assertEqual(
23 {u"environment-uuid": "DEAD-BEEF",
24 u"unit-name": "service/0",
25 u"api-addresses": ["10.0.3.1:17070"],
26 u"private-address": "127.0.0.1"}, juju_info)
27
28 def test_get_juju_info_empty_file(self):
29 """
30 If L{get_juju_info} is called with a configuration pointing to
31 an empty file, it returns C{None}.
32 """
33 stub_config = self.Config(self.makeFile(""))
34 juju_info = get_juju_info(stub_config)
35 self.log_helper.ignore_errors(ValueError)
36 self.assertEqual(juju_info, None)
37 self.assertIn("Error attempting to read JSON", self.logfile.getvalue())
38
39 def test_get_juju_info_not_a_file(self):
40 """
41 If L{get_juju_info} is called with a configuration pointing to
42 a directory, it returns C{None}.
43 """
44 stub_config = self.Config("/")
45 juju_info = get_juju_info(stub_config)
46 self.assertIs(juju_info, None)
47
48 def test_get_juju_info_multiple_endpoints(self):
49 """L{get_juju_info} turns space separated API addresses into a list."""
50 juju_multiple_endpoints = json.dumps({
51 "environment-uuid": "DEAD-BEEF",
52 "unit-name": "service/0",
53 "api-addresses": "10.0.3.1:17070 10.0.3.2:18080",
54 "private-address": "127.0.0.1"})
55
56 stub_config = self.Config(self.makeFile(juju_multiple_endpoints))
57 juju_info = get_juju_info(stub_config)
58 self.assertEqual(
59 {u"environment-uuid": "DEAD-BEEF",
60 u"unit-name": "service/0",
61 u"api-addresses": ["10.0.3.1:17070", "10.0.3.2:18080"],
62 u"private-address": "127.0.0.1"}, juju_info)
063
=== modified file 'landscape/message_schemas.py'
--- landscape/message_schemas.py 2013-09-25 08:43:53 +0000
+++ landscape/message_schemas.py 2013-09-27 14:18:49 +0000
@@ -103,8 +103,12 @@
103HARDWARE_INFO = Message("hardware-info", {103HARDWARE_INFO = Message("hardware-info", {
104 "data": utf8})104 "data": utf8})
105105
106JUJU_INFO = Message("juju-info", {106juju_data = {"environment-uuid": utf8,
107 "data": Dict(utf8, utf8)})107 "api-addresses": List(utf8),
108 "unit-name": utf8}
109
110# The copy is needed because Message mutates the dictionary
111JUJU_INFO = Message("juju-info", juju_data.copy())
108112
109LOAD_AVERAGE = Message("load-average", {113LOAD_AVERAGE = Message("load-average", {
110 "load-averages": List(Tuple(Int(), Float())),114 "load-averages": List(Tuple(Int(), Float())),
@@ -171,9 +175,11 @@
171 "hostname": utf8,175 "hostname": utf8,
172 "account_name": utf8,176 "account_name": utf8,
173 "tags": Any(utf8, Constant(None)),177 "tags": Any(utf8, Constant(None)),
174 "vm-info": String()},178 "vm-info": String(),
179 "juju-info": KeyDict(juju_data)},
175 # hostname wasn't around in old versions180 # hostname wasn't around in old versions
176 optional=["registration_password", "hostname", "tags", "vm-info"])181 optional=["registration_password", "hostname", "tags", "vm-info",
182 "juju-info"])
177183
178184
179REGISTER_PROVISIONED_MACHINE = Message(185REGISTER_PROVISIONED_MACHINE = Message(
180186
=== modified file 'landscape/monitor/config.py'
--- landscape/monitor/config.py 2013-09-25 14:06:05 +0000
+++ landscape/monitor/config.py 2013-09-27 14:18:49 +0000
@@ -1,5 +1,3 @@
1import os.path
2
3from landscape.deployment import Configuration1from landscape.deployment import Configuration
42
53
@@ -32,8 +30,3 @@
32 if self.monitor_plugins == "ALL":30 if self.monitor_plugins == "ALL":
33 return ALL_PLUGINS31 return ALL_PLUGINS
34 return [x.strip() for x in self.monitor_plugins.split(",")]32 return [x.strip() for x in self.monitor_plugins.split(",")]
35
36 @property
37 def juju_filename(self):
38 """Get the path to the Juju JSON file."""
39 return os.path.join(self.data_path, "juju-info.json")
4033
=== modified file 'landscape/monitor/jujuinfo.py'
--- landscape/monitor/jujuinfo.py 2013-09-25 14:06:05 +0000
+++ landscape/monitor/jujuinfo.py 2013-09-27 14:18:49 +0000
@@ -1,8 +1,6 @@
1import json
2import logging1import logging
3import os.path
42
5from landscape.lib.fs import read_file3from landscape.lib.juju import get_juju_info
6from landscape.monitor.plugin import MonitorPlugin4from landscape.monitor.plugin import MonitorPlugin
75
86
@@ -21,32 +19,16 @@
21 broker.call_if_accepted("juju-info", self.send_juju_message, urgent)19 broker.call_if_accepted("juju-info", self.send_juju_message, urgent)
2220
23 def send_juju_message(self, urgent=False):21 def send_juju_message(self, urgent=False):
24 message = {}22 message = self._create_juju_info_message()
25 juju_data = self._create_juju_info_message()23 if message:
26 if juju_data:
27 message["type"] = "juju-info"24 message["type"] = "juju-info"
28 message["data"] = juju_data
29 logging.info("Queuing message with updated juju info.")25 logging.info("Queuing message with updated juju info.")
30 self.registry.broker.send_message(message, self._session_id,26 self.registry.broker.send_message(message, self._session_id,
31 urgent=urgent)27 urgent=urgent)
3228
33 def _create_juju_info_message(self):29 def _create_juju_info_message(self):
34 message = self._get_juju_info()30 message = get_juju_info(self.registry.config)
35 if message != self._persist.get("juju-info"):31 if message != self._persist.get("juju-info"):
36 self._persist.set("juju-info", message)32 self._persist.set("juju-info", message)
37 return message33 return message
38 return None34 return None
39
40 def _get_juju_info(self):
41 juju_filename = self.registry.config.juju_filename
42 if not os.path.isfile(juju_filename):
43 return None
44 json_contents = read_file(juju_filename)
45 try:
46 juju_info = json.loads(json_contents)
47 except Exception:
48 logging.exception(
49 "Error attempting to read JSON from %s" % juju_filename)
50 return None
51 else:
52 return juju_info
5335
=== modified file 'landscape/monitor/tests/test_config.py'
--- landscape/monitor/tests/test_config.py 2013-09-25 14:06:05 +0000
+++ landscape/monitor/tests/test_config.py 2013-09-27 14:18:49 +0000
@@ -1,5 +1,3 @@
1import os.path
2
3from landscape.tests.helpers import LandscapeTest1from landscape.tests.helpers import LandscapeTest
4from landscape.monitor.config import MonitorConfiguration, ALL_PLUGINS2from landscape.monitor.config import MonitorConfiguration, ALL_PLUGINS
53
@@ -32,12 +30,3 @@
32 """30 """
33 self.config.load(["--flush-interval", "123"])31 self.config.load(["--flush-interval", "123"])
34 self.assertEqual(self.config.flush_interval, 123)32 self.assertEqual(self.config.flush_interval, 123)
35
36 def test_juju_filename(self):
37 """
38 Can get path to juju JSON file from config.
39 """
40 self.config.data_path = self.makeDir()
41 self.assertEqual(self.config.juju_filename, os.path.join(
42 self.config.data_path, "juju-info.json"))
43
4433
=== modified file 'landscape/monitor/tests/test_jujuinfo.py'
--- landscape/monitor/tests/test_jujuinfo.py 2013-09-25 14:06:05 +0000
+++ landscape/monitor/tests/test_jujuinfo.py 2013-09-27 14:18:49 +0000
@@ -30,10 +30,9 @@
30 self.plugin.exchange()30 self.plugin.exchange()
31 message = self.mstore.get_pending_messages()[0]31 message = self.mstore.get_pending_messages()[0]
32 self.assertEqual(message["type"], "juju-info")32 self.assertEqual(message["type"], "juju-info")
33 self.assertEqual(message["data"]["environment-uuid"], "DEAD-BEEF")33 self.assertEqual(message["environment-uuid"], "DEAD-BEEF")
34 self.assertEqual(message["data"]["unit-name"], "juju-unit-name")34 self.assertEqual(message["unit-name"], "juju-unit-name")
35 self.assertEqual(message["data"]["api-addresses"], "10.0.3.1:17070")35 self.assertEqual(message["api-addresses"], ["10.0.3.1:17070"])
36 self.assertEqual(message["data"]["private-address"], "127.0.0.1")
3736
38 def test_juju_info_reported_only_once(self):37 def test_juju_info_reported_only_once(self):
39 """38 """
@@ -57,10 +56,9 @@
57 self.plugin.exchange()56 self.plugin.exchange()
58 message = self.mstore.get_pending_messages()[0]57 message = self.mstore.get_pending_messages()[0]
59 self.assertEqual(message["type"], "juju-info")58 self.assertEqual(message["type"], "juju-info")
60 self.assertEqual(message["data"]["environment-uuid"], "DEAD-BEEF")59 self.assertEqual(message["environment-uuid"], "DEAD-BEEF")
61 self.assertEqual(message["data"]["unit-name"], "juju-unit-name")60 self.assertEqual(message["unit-name"], "juju-unit-name")
62 self.assertEqual(message["data"]["api-addresses"], "10.0.3.1:17070")61 self.assertEqual(message["api-addresses"], ["10.0.3.1:17070"])
63 self.assertEqual(message["data"]["private-address"], "127.0.0.1")
6462
65 self.makeFile(63 self.makeFile(
66 json.dumps({"environment-uuid": "FEED-BEEF",64 json.dumps({"environment-uuid": "FEED-BEEF",
@@ -71,10 +69,9 @@
71 self.plugin.exchange()69 self.plugin.exchange()
72 message = self.mstore.get_pending_messages()[1]70 message = self.mstore.get_pending_messages()[1]
73 self.assertEqual(message["type"], "juju-info")71 self.assertEqual(message["type"], "juju-info")
74 self.assertEqual(message["data"]["environment-uuid"], "FEED-BEEF")72 self.assertEqual(message["environment-uuid"], "FEED-BEEF")
75 self.assertEqual(message["data"]["unit-name"], "changed-unit-name")73 self.assertEqual(message["unit-name"], "changed-unit-name")
76 self.assertEqual(message["data"]["api-addresses"], "10.0.3.2:17070")74 self.assertEqual(message["api-addresses"], ["10.0.3.2:17070"])
77 self.assertEqual(message["data"]["private-address"], "127.0.1.1")
7875
79 def test_no_message_with_invalid_json(self):76 def test_no_message_with_invalid_json(self):
80 """No Juju message is sent if the JSON file is invalid."""77 """No Juju message is sent if the JSON file is invalid."""
8178
=== modified file 'landscape/tests/test_deployment.py'
--- landscape/tests/test_deployment.py 2013-09-25 09:24:17 +0000
+++ landscape/tests/test_deployment.py 2013-09-27 14:18:49 +0000
@@ -560,7 +560,7 @@
560560
561 If a specific config file is requested, use this instead of defaults.561 If a specific config file is requested, use this instead of defaults.
562562
563 If a cmdilne --config option is specified this should take precedence563 If a cmdline --config option is specified this should take precedence
564 over either of the former options.564 over either of the former options.
565 """565 """
566 default_filename1 = self.makeFile("")566 default_filename1 = self.makeFile("")
@@ -599,8 +599,9 @@
599 The L{Configuration.socket_path} property returns the path to the599 The L{Configuration.socket_path} property returns the path to the
600 socket directory.600 socket directory.
601 """601 """
602 self.assertEqual(self.config.sockets_path,602 self.assertEqual(
603 "/var/lib/landscape/client/sockets")603 "/var/lib/landscape/client/sockets",
604 self.config.sockets_path)
604605
605 def test_annotations_path(self):606 def test_annotations_path(self):
606 """607 """
@@ -611,6 +612,15 @@
611 "/var/lib/landscape/client/annotations.d",612 "/var/lib/landscape/client/annotations.d",
612 self.config.annotations_path)613 self.config.annotations_path)
613614
615 def test_juju_filename(self):
616 """
617 The L{Configuration.juju_filename} property returns the path to the
618 juju info file.
619 """
620 self.assertEqual(
621 "/var/lib/landscape/client/juju-info.json",
622 self.config.juju_filename)
623
614 def test_clone(self):624 def test_clone(self):
615 """The L{Configuration.clone} method clones a configuration."""625 """The L{Configuration.clone} method clones a configuration."""
616 self.write_config_file()626 self.write_config_file()

Subscribers

People subscribed via source and target branches

to all changes: