Merge lp:~frankban/juju-deployer/missing-units into lp:juju-deployer

Proposed by Francesco Banconi
Status: Merged
Approved by: David Britton
Approved revision: 142
Merged at revision: 140
Proposed branch: lp:~frankban/juju-deployer/missing-units
Merge into: lp:juju-deployer
Diff against target: 244 lines (+59/-43)
7 files modified
deployer/service.py (+4/-0)
deployer/tests/base.py (+2/-0)
deployer/tests/test_data/stack-placement-invalid-subordinate.yaml (+11/-0)
deployer/tests/test_deployment.py (+27/-20)
deployer/tests/test_diff.py (+4/-6)
deployer/tests/test_guiserver.py (+6/-8)
deployer/tests/test_importer.py (+5/-9)
To merge this branch: bzr merge lp:~frankban/juju-deployer/missing-units
Reviewer Review Type Date Requested Status
David Britton (community) Approve
Brad Crittenden (community) code Approve
Madison Scott-Clary (community) Approve
Review via email: mp+252935@code.launchpad.net

Commit message

Disallow deploying a bundle when a service unit placement points to a subordinate service.

Description of the change

Disallow deploying a bundle when a service unit placement points to a subordinate service.

Without this kind of validation, the bundle would still fail later with an unhelpful KeyError: 'units'.

The big assumption here is that we want the deployer to fail in such cases. Do we? If not, what's the correct behavior?

To post a comment you must log in.
Revision history for this message
Madison Scott-Clary (makyo) wrote :

LGTM, thanks for the fix. Per IRC, I believe that the placement directive should specify the unit on which the subordinate is deployed, rather than the subordinate unit itself, since we can't guarantee the order of subordinate deployments, thus can't guarantee which machine it would wind up on.

review: Approve
Revision history for this message
Madison Scott-Clary (makyo) wrote :

NB: this should land before https://code.launchpad.net/~makyo/juju-deployer/machines-and-placement/+merge/251857 and these changes made in that MP.

Revision history for this message
Brad Crittenden (bac) wrote :

LGTM. No QA

review: Approve (code)
Revision history for this message
David Britton (dpb) wrote :

Thanks -- I've marked as Approved, but put the MP back to WIP, please change back to 'needs review' when you have fixed up the one test issue, and I'll land it.

Code otherwise looks good.

Thanks for doing this, much friendlier error.

review: Approve
Revision history for this message
Francesco Banconi (frankban) wrote :

Thank you all for the reviews!

The fetching tests should now be properly skipped as suggested by David.
I realized the get_named_deployment is already defined in the Base test case, so I removed it from DeploymentTest. I also defined a helper skip decorator in Base to avoid repetition.
I also pushed some lint drive by fixes on the modified files.
Please take another look.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'deployer/service.py'
--- deployer/service.py 2015-01-22 15:22:18 +0000
+++ deployer/service.py 2015-03-17 10:41:55 +0000
@@ -106,6 +106,10 @@
106 feedback.error(106 feedback.error(
107 "Nested placement not supported %s -> %s -> %s" % (107 "Nested placement not supported %s -> %s -> %s" % (
108 self.service.name, p, services[p].unit_placement))108 self.service.name, p, services[p].unit_placement))
109 elif self.deployment.get_charm_for(p).is_subordinate():
110 feedback.error(
111 "Cannot place to a subordinate service: %s -> %s" % (
112 self.service.name, p))
109 else:113 else:
110 feedback.error(114 feedback.error(
111 "Invalid service placement %s to %s" % (115 "Invalid service placement %s to %s" % (
112116
=== modified file 'deployer/tests/base.py'
--- deployer/tests/base.py 2014-01-23 12:55:11 +0000
+++ deployer/tests/base.py 2015-03-17 10:41:55 +0000
@@ -11,6 +11,8 @@
1111
1212
13TEST_OFFLINE = ("DEB_BUILD_ARCH" in os.environ or "TEST_OFFLINE" in os.environ)13TEST_OFFLINE = ("DEB_BUILD_ARCH" in os.environ or "TEST_OFFLINE" in os.environ)
14skip_if_offline = unittest.skipIf(
15 TEST_OFFLINE, 'Requires configured bzr launchpad id and network access')
1416
1517
16class Base(unittest.TestCase):18class Base(unittest.TestCase):
1719
=== added file 'deployer/tests/test_data/stack-placement-invalid-subordinate.yaml'
--- deployer/tests/test_data/stack-placement-invalid-subordinate.yaml 1970-01-01 00:00:00 +0000
+++ deployer/tests/test_data/stack-placement-invalid-subordinate.yaml 2015-03-17 10:41:55 +0000
@@ -0,0 +1,11 @@
1stack:
2 series: precise
3 services:
4 nova-compute:
5 charm: cs:precise/nova-compute
6 to: nrpe
7 ceph:
8 to: lxc:nrpe=0
9 nrpe:
10 charm: cs:precise/nrpe
11 units: 2
012
=== modified file 'deployer/tests/test_deployment.py'
--- deployer/tests/test_deployment.py 2015-01-22 15:22:18 +0000
+++ deployer/tests/test_deployment.py 2015-03-17 10:41:55 +0000
@@ -1,14 +1,11 @@
1import base641import base64
2import StringIO2import StringIO
3import os3import os
4import unittest4
5
6
7from deployer.config import ConfigStack
8from deployer.deployment import Deployment5from deployer.deployment import Deployment
9from deployer.utils import setup_logging, ErrorExit6from deployer.utils import setup_logging, ErrorExit
107
11from .base import Base, TEST_OFFLINE8from .base import Base, skip_if_offline
129
1310
14class DeploymentTest(Base):11class DeploymentTest(Base):
@@ -17,17 +14,19 @@
17 self.output = setup_logging(14 self.output = setup_logging(
18 debug=True, verbose=True, stream=StringIO.StringIO())15 debug=True, verbose=True, stream=StringIO.StringIO())
1916
20 def get_named_deployment(self, file_name, stack_name):17 def get_named_deployment_and_fetch(self, file_name, stack_name):
21 return ConfigStack(18 deployment = self.get_named_deployment(file_name, stack_name)
22 [os.path.join(19 # Fetch charms in order to allow proper late binding config and
23 self.test_data_dir, file_name)]).get(stack_name)20 # placement validation.
21 repo_path = self.mkdir()
22 os.mkdir(os.path.join(repo_path, "precise"))
23 deployment.repo_path = repo_path
24 deployment.fetch_charms()
25 return deployment
2426
25 @unittest.skipIf(TEST_OFFLINE,27 @skip_if_offline
26 "Requires configured bzr launchpad id and network access")
27 def test_deployer(self):28 def test_deployer(self):
28 d = ConfigStack(29 d = self.get_named_deployment_and_fetch('blog.yaml', 'wordpress-prod')
29 [os.path.join(
30 self.test_data_dir, "blog.yaml")]).get('wordpress-prod')
31 services = d.get_services()30 services = d.get_services()
32 self.assertTrue([s for s in services if s.name == "newrelic"])31 self.assertTrue([s for s in services if s.name == "newrelic"])
3332
@@ -36,12 +35,6 @@
36 d.data['inherits'],35 d.data['inherits'],
37 ['wordpress-stage', 'wordpress-base', 'metrics-base'])36 ['wordpress-stage', 'wordpress-base', 'metrics-base'])
3837
39 # Fetch charms to verify late binding config values & validation.
40 t = self.mkdir()
41 os.mkdir(os.path.join(t, "precise"))
42 d.repo_path = t
43 d.fetch_charms()
44
45 # Load up overrides and resolves38 # Load up overrides and resolves
46 d.load_overrides(["key=abc"])39 d.load_overrides(["key=abc"])
47 d.resolve()40 d.resolve()
@@ -99,6 +92,20 @@
99 else:92 else:
100 self.fail("Should fail")93 self.fail("Should fail")
10194
95 @skip_if_offline
96 def test_validate_invalid_placement_subordinate(self):
97 # Placement validation fails if a subordinate charm is provided.
98 deployment = self.get_named_deployment_and_fetch(
99 'stack-placement-invalid-subordinate.yaml', 'stack')
100 with self.assertRaises(ErrorExit):
101 deployment.validate_placement()
102 output = self.output.getvalue()
103 self.assertIn(
104 'Cannot place to a subordinate service: ceph -> nrpe\n', output)
105 self.assertIn(
106 'Cannot place to a subordinate service: nova-compute -> nrpe\n',
107 output)
108
102 def test_get_unit_placement(self):109 def test_get_unit_placement(self):
103 d = self.get_named_deployment("stack-placement.yaml", "stack")110 d = self.get_named_deployment("stack-placement.yaml", "stack")
104 status = {111 status = {
105112
=== modified file 'deployer/tests/test_diff.py'
--- deployer/tests/test_diff.py 2014-01-23 12:40:57 +0000
+++ deployer/tests/test_diff.py 2015-03-17 10:41:55 +0000
@@ -2,20 +2,18 @@
2# pylint: disable=C01032# pylint: disable=C0103
3import StringIO3import StringIO
4import os4import os
5import unittest
6import shutil5import shutil
7import tempfile6import tempfile
7
8from deployer.config import ConfigStack
8from deployer.env.mem import MemoryEnvironment9from deployer.env.mem import MemoryEnvironment
9from deployer.config import ConfigStack
10from deployer.utils import setup_logging10from deployer.utils import setup_logging
1111
12from .base import Base, TEST_OFFLINE12from .base import Base, skip_if_offline
13from ..action.diff import Diff13from ..action.diff import Diff
1414
1515
16# pylint: disable=C0111, R090416@skip_if_offline
17@unittest.skipIf(TEST_OFFLINE,
18 "Requires configured bzr launchpad id and network access")
19class DiffTest(Base):17class DiffTest(Base):
2018
21 def setUp(self):19 def setUp(self):
2220
=== modified file 'deployer/tests/test_guiserver.py'
--- deployer/tests/test_guiserver.py 2015-02-03 12:06:59 +0000
+++ deployer/tests/test_guiserver.py 2015-03-17 10:41:55 +0000
@@ -11,7 +11,7 @@
1111
12from deployer import guiserver12from deployer import guiserver
13from deployer.feedback import Feedback13from deployer.feedback import Feedback
14from deployer.tests.base import TEST_OFFLINE14from deployer.tests.base import skip_if_offline
1515
1616
17class TestGetDefaultGuiserverOptions(unittest.TestCase):17class TestGetDefaultGuiserverOptions(unittest.TestCase):
@@ -26,9 +26,9 @@
26 'bootstrap', 'branch_only', 'configs', 'debug', 'deploy_delay',26 'bootstrap', 'branch_only', 'configs', 'debug', 'deploy_delay',
27 'deployment', 'description', 'destroy_services', 'diff',27 'deployment', 'description', 'destroy_services', 'diff',
28 'find_service', 'ignore_errors', 'juju_env', 'list_deploys',28 'find_service', 'ignore_errors', 'juju_env', 'list_deploys',
29 'no_local_mods', 'no_relations', 'overrides', 'rel_wait', 'retry_count', 'series',29 'no_local_mods', 'no_relations', 'overrides', 'rel_wait',
30 'skip_unit_wait', 'terminate_machines', 'timeout', 'update_charms',30 'retry_count', 'series', 'skip_unit_wait', 'terminate_machines',
31 'verbose', 'watch'31 'timeout', 'update_charms', 'verbose', 'watch'
32 ])32 ])
33 self.assertEqual(expected_keys, set(self.options.__dict__.keys()))33 self.assertEqual(expected_keys, set(self.options.__dict__.keys()))
3434
@@ -172,8 +172,7 @@
172 mock_env_instance.close.assert_called_once_with()172 mock_env_instance.close.assert_called_once_with()
173173
174174
175@unittest.skipIf(TEST_OFFLINE,175@skip_if_offline
176 "Requires configured bzr launchpad id and network access")
177@mock.patch('deployer.guiserver.GUIEnvironment')176@mock.patch('deployer.guiserver.GUIEnvironment')
178class TestValidate(DeployerFunctionsTestMixin, unittest.TestCase):177class TestValidate(DeployerFunctionsTestMixin, unittest.TestCase):
179178
@@ -192,8 +191,7 @@
192 self.apiurl, self.username, self.password, self.bundle)191 self.apiurl, self.username, self.password, self.bundle)
193192
194193
195@unittest.skipIf(TEST_OFFLINE,194@skip_if_offline
196 "Requires configured bzr launchpad id and network access")
197@mock.patch('deployer.guiserver.GUIEnvironment')195@mock.patch('deployer.guiserver.GUIEnvironment')
198class TestImportBundle(DeployerFunctionsTestMixin, unittest.TestCase):196class TestImportBundle(DeployerFunctionsTestMixin, unittest.TestCase):
199197
200198
=== modified file 'deployer/tests/test_importer.py'
--- deployer/tests/test_importer.py 2014-10-01 10:18:36 +0000
+++ deployer/tests/test_importer.py 2015-03-17 10:41:55 +0000
@@ -1,14 +1,12 @@
1import logging1import os
2
2import mock3import mock
3import os
4import sys
5import unittest
64
7from deployer.config import ConfigStack5from deployer.config import ConfigStack
8from deployer.action.importer import Importer6from deployer.action.importer import Importer
9from deployer.utils import yaml_dump, yaml_load7from deployer.utils import yaml_dump, yaml_load
108
11from base import Base, TEST_OFFLINE9from base import Base, skip_if_offline
1210
1311
14class Options(dict):12class Options(dict):
@@ -46,8 +44,7 @@
46 'verbose': True,44 'verbose': True,
47 'watch': False})45 'watch': False})
4846
49 @unittest.skipIf(TEST_OFFLINE,47 @skip_if_offline
50 "Requires configured bzr launchpad id and network access")
51 @mock.patch('deployer.action.importer.time')48 @mock.patch('deployer.action.importer.time')
52 def test_importer(self, mock_time):49 def test_importer(self, mock_time):
53 # Trying to track down where this comes from http://pad.lv/124382750 # Trying to track down where this comes from http://pad.lv/1243827
@@ -67,8 +64,7 @@
67 yaml_load(yaml_dump(yaml_load(yaml_dump(config)))),64 yaml_load(yaml_dump(yaml_load(yaml_dump(config)))),
68 config)65 config)
6966
70 @unittest.skipIf(TEST_OFFLINE,67 @skip_if_offline
71 "Requires configured bzr launchpad id and network access")
72 @mock.patch('deployer.action.importer.time')68 @mock.patch('deployer.action.importer.time')
73 def test_importer_no_relations(self, mock_time):69 def test_importer_no_relations(self, mock_time):
74 self.options.no_relations = True70 self.options.no_relations = True

Subscribers

People subscribed via source and target branches