Merge lp:~gz/juju-ci-tools/endpoints_bindings into lp:juju-ci-tools

Proposed by Martin Packman
Status: Merged
Approved by: Martin Packman
Approved revision: 1660
Merged at revision: 1675
Proposed branch: lp:~gz/juju-ci-tools/endpoints_bindings
Merge into: lp:juju-ci-tools
Diff against target: 708 lines (+586/-6)
6 files modified
assess_endpoint_bindings.py (+344/-0)
jujupy.py (+23/-5)
substrate.py (+22/-0)
tests/test_assess_endpoint_bindings.py (+177/-0)
tests/test_jujupy.py (+2/-1)
tests/test_substrate.py (+18/-0)
To merge this branch: bzr merge lp:~gz/juju-ci-tools/endpoints_bindings
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+308923@code.launchpad.net

Description of the change

Work in progress on making endpoint bindings test that can be run on shared maas

Basic outline is that the networking parts are set up and torn down using the underlying maas commands each run, and that machines that need interfaces reconfigured are constrained from being used on other maas jobs. There are still some details here that need discussing in more depth, but the basic outline here should provide a start.

I have chopped out most of the test complexity that I was adding locally for ease of review. Also, had to make some changes to how the network configuration was done in light of how finfolk is actually set up, so for now have deleted the tests based on real data for the maas configuration, will update with the new behaviour and re-add.

There's a bunch more to talk about here as well, happy to answer any questions.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you. I have some advice, questions, and comments inline. Nothing blocks this branch from merging.

review: Approve (code)
Revision history for this message
Martin Packman (gz) wrote :

Thanks for all the comments, have replied in line and with push some changes.

1659. By Martin Packman

Address review comments from sinzui and use persistent spaces

1660. By Martin Packman

Give gateway_ip when creating subnets

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'assess_endpoint_bindings.py'
--- assess_endpoint_bindings.py 1970-01-01 00:00:00 +0000
+++ assess_endpoint_bindings.py 2016-10-21 22:18:30 +0000
@@ -0,0 +1,344 @@
1#!/usr/bin/env python
2"""Validate endpoint bindings functionality on MAAS."""
3
4from __future__ import print_function
5
6import argparse
7import contextlib
8import logging
9import os
10import sys
11import yaml
12
13from deploy_stack import (
14 BootstrapManager,
15)
16from jujucharm import (
17 Charm,
18)
19from substrate import (
20 maas_account_from_config,
21)
22from utility import (
23 add_basic_testing_arguments,
24 configure_logging,
25 temp_dir,
26)
27
28
29log = logging.getLogger("assess_endpoint_bindings")
30
31
32script_identifier = "endpoint-bindings"
33
34# To avoid clashes with other tests these space names must be seperately
35# registered in jujupy to populate constraints.
36space_control = script_identifier + "-control"
37space_data = script_identifier + "-data"
38space_public = script_identifier + "-public"
39
40
41def _generate_vids(start=10):
42 """
43 Generate a series of vid values beginning with start.
44
45 Ideally these values would be carefully chosen to not clash with existing
46 vlans, but for now just hardcode.
47 """
48 for vid in range(start, 4096):
49 yield vid
50
51
52def _generate_cidrs(start=40, inc=10, block_pattern="10.0.{}.0/24"):
53 """
54 Generate a series of cidrs based on block_pattern beginning with start.
55
56 Would be good not to hardcode but inspecting network for free ranges is
57 also non-trivial.
58 """
59 for n in range(start, 255, inc):
60 yield block_pattern.format(n)
61
62
63def ensure_spaces(manager, required_spaces):
64 """Return details for each given required_spaces creating spaces as needed.
65
66 :param manager: MAAS account manager.
67 :param required_spaces: List of space names that may need to be created.
68 """
69 existing_spaces = manager.spaces()
70 log.info("Have spaces: %s", ", ".join(s["name"] for s in existing_spaces))
71 spaces_map = dict((s["name"], s) for s in existing_spaces)
72 spaces = []
73 for space_name in required_spaces:
74 space = spaces_map.get(space_name)
75 if space is None:
76 space = manager.create_space(space_name)
77 log.info("Created space: %r", space)
78 spaces.append(space)
79 return spaces
80
81
82@contextlib.contextmanager
83def reconfigure_networking(manager, required_spaces):
84 """Create new MAAS networking primitives to prepare for testing.
85
86 :param manager: MAAS account manager.
87 :param required_spaces: List of spaces to make with vlans and subnets.
88 """
89 new_subnets = []
90 new_vlans = []
91 fabrics = manager.fabrics()
92 log.info("Have fabrics: %s", ", ".join(f["name"] for f in fabrics))
93 new_fabric = manager.create_fabric(script_identifier)
94 try:
95 log.info("Created fabric: %r", new_fabric)
96
97 spaces = ensure_spaces(manager, required_spaces)
98
99 for vid, space_name in zip(_generate_vids(), required_spaces):
100 name = space_name + "-vlan"
101 new_vlans.append(manager.create_vlan(new_fabric["id"], vid, name))
102 log.info("Created vlan: %r", new_vlans[-1])
103
104 for cidr, vlan, space in zip(_generate_cidrs(), new_vlans, spaces):
105 new_subnets.append(manager.create_subnet(
106 cidr, fabric_id=new_fabric["id"], vlan_id=vlan["id"],
107 space=space["id"], gateway_ip=cidr.replace(".0/24", ".1")))
108 log.info("Created subnet: %r", new_subnets[-1])
109
110 yield new_fabric, spaces, list(new_vlans), list(new_subnets)
111
112 finally:
113 for subnet in new_subnets:
114 manager.delete_subnet(subnet["id"])
115 log.info("Deleted subnet: %s", subnet["name"])
116
117 for vlan in new_vlans:
118 manager.delete_vlan(new_fabric["id"], vlan["vid"])
119 log.info("Deleted vlan: %s", vlan["name"])
120
121 try:
122 manager.delete_fabric(new_fabric["id"])
123 except Exception:
124 log.exception("Failed to delete fabric: %s", new_fabric["id"])
125 raise
126 else:
127 log.info("Deleted fabric: %s", new_fabric["id"])
128
129
130@contextlib.contextmanager
131def reconfigure_machines(manager, fabric, required_machine_subnets):
132 """
133 Reconfigure MAAS machines with new interfaces to prepare for testing.
134
135 There are some unavoidable races if multiple jobs attempt to reconfigure
136 machines at the same time. Also, in heterogenous environments an
137 inadequate machine may be reserved at this time.
138
139 Ideally this function would just allocate some machines before operating
140 on them. Alas, MAAS doesn't allow interface changes on allocated machines
141 and Juju will not select them for deployment.
142
143 :param manager: MAAS account manager.
144 :param fabric: Data from MAAS about the fabric to be used.
145 :param required_machine_subnets: List of list of vlan and subnet ids.
146 """
147
148 # Find all machines not currently being used
149 all_machines = manager.machines()
150 candidate_machines = [
151 m for m in all_machines if m["status"] == manager.STATUS_READY]
152 # Take the id of the default vlan on the new fabric
153 default_vlan = fabric["vlans"][0]["id"]
154
155 configured_machines = []
156 machine_interfaces = {}
157 try:
158 for machine_subnets in required_machine_subnets:
159 if not candidate_machines:
160 raise Exception("No ready maas machines to configure")
161
162 machine = candidate_machines.pop()
163 system_id = machine["system_id"]
164 # TODO(gz): Add logic to pick sane parent?
165 existing_interface = [
166 interface for interface in machine["interface_set"]
167 if not any("subnet" in link for link in interface["links"])
168 ][0]
169 previous_vlan_id = existing_interface["vlan"]["id"]
170 new_interfaces = []
171 machine_interfaces[system_id] = (
172 existing_interface, previous_vlan_id, new_interfaces)
173 manager.interface_update(
174 system_id, existing_interface["id"], vlan_id=default_vlan)
175 log.info("Changed existing interface: %s %s", system_id,
176 existing_interface["name"])
177 parent = existing_interface["id"]
178
179 for vlan_id, subnet_id in machine_subnets:
180 links = []
181 interface = manager.interface_create_vlan(
182 system_id, parent, vlan_id)
183 new_interfaces.append(interface)
184 log.info("Created interface: %r", interface)
185
186 updated_subnet = manager.interface_link_subnet(
187 system_id, interface["id"], "AUTO", subnet_id)
188 # TODO(gz): Need to pick out right link if multiple are added.
189 links.append(updated_subnet["links"][0])
190 log.info("Created link: %r", links[-1])
191
192 configured_machines.append(machine)
193 yield configured_machines
194 finally:
195 log.info("About to reset machine interfaces to original states.")
196 for system_id in machine_interfaces:
197 parent, vlan, children = machine_interfaces[system_id]
198 for child in children:
199 manager.delete_interface(system_id, child["id"])
200 log.info("Deleted interface: %s %s", system_id, child["id"])
201 manager.interface_update(system_id, parent["id"], vlan_id=vlan)
202 log.info("Reset original interface: %s", parent["name"])
203
204
205def create_test_charms():
206 """Create charms for testing and bundle using them."""
207 charm_datastore = Charm("datastore", "Testing datastore charm.")
208 charm_datastore.metadata["provides"] = {
209 "datastore": {"interface": "data"},
210 }
211
212 charm_frontend = Charm("frontend", "Testing frontend charm.")
213 charm_frontend.metadata["provides"] = {
214 "website": {"interface": "http"},
215 }
216 charm_frontend.metadata["requires"] = {
217 "datastore": {"interface": "data"},
218 }
219
220 bundle = {
221 "services": {
222 "datastore": {
223 "charm": "./xenial/datastore",
224 "series": "xenial",
225 "num-units": 1,
226 "bindings": {
227 "datastore": space_data,
228 },
229 },
230 "frontend": {
231 "charm": "./xenial/frontend",
232 "series": "xenial",
233 "num-units": 1,
234 "bindings": {
235 "website": space_public,
236 "datastore": space_data,
237 },
238 },
239 },
240 "relations": [
241 ["datastore:datastore", "frontend:datastore"],
242 ],
243 }
244 return bundle, [charm_datastore, charm_frontend]
245
246
247@contextlib.contextmanager
248def using_bundle_and_charms(bundle, charms, bundle_name="bundle.yaml"):
249 """Commit bundle and charms to disk and gives path to bundle."""
250 with temp_dir() as working_dir:
251 for charm in charms:
252 charm.to_repo_dir(working_dir)
253
254 # TODO(gz): Create a bundle abstration in jujucharm module
255 bundle_path = os.path.join(working_dir, bundle_name)
256 with open(bundle_path, "w") as f:
257 yaml.safe_dump(bundle, f)
258
259 yield bundle_path
260
261
262def machine_spaces_for_bundle(bundle):
263 """Return a list of sets of spaces required for machines in bundle."""
264 machines = []
265 for service in bundle["services"].values():
266 spaces = frozenset(service.get("bindings", {}).values())
267 num_units = service.get("num_units", 1)
268 machines.extend([spaces] * num_units)
269 return machines
270
271
272def bootstrap_and_test(bootstrap_manager, bundle_path, machine):
273 with bootstrap_manager.booted_context(False, to=machine):
274 client = bootstrap_manager.client
275 log.info("Deploying bundle.")
276 client.deploy(bundle_path)
277 log.info("Waiting for all units to start.")
278 client.wait_for_started()
279 client.wait_for_workloads()
280 log.info("Validating bindings.")
281 validate(client)
282
283
284def validate(client):
285 """Ensure relations are bound to the correct spaces."""
286
287
288def assess_endpoint_bindings(maas_manager, bootstrap_manager):
289 required_spaces = [space_data, space_public]
290
291 bundle, charms = create_test_charms()
292
293 machine_spaces = machine_spaces_for_bundle(bundle)
294 # Add a bootstrap machine in all spaces
295 machine_spaces.insert(0, frozenset().union(*machine_spaces))
296
297 log.info("About to write charms to disk.")
298 with using_bundle_and_charms(bundle, charms) as bundle_path:
299 log.info("About to reconfigure maas networking.")
300 with reconfigure_networking(maas_manager, required_spaces) as nets:
301
302 fabric, spaces, vlans, subnets = nets
303 # Derive the vlans and subnets that need to be added to machines
304 vlan_subnet_per_machine = []
305 for spaces in machine_spaces:
306 idxs = sorted(required_spaces.index(space) for space in spaces)
307 vlans_subnets = [
308 (vlans[i]["id"], subnets[i]["id"]) for i in idxs]
309 vlan_subnet_per_machine.append(vlans_subnets)
310
311 log.info("About to add new interfaces to machines.")
312 with reconfigure_machines(
313 maas_manager, fabric, vlan_subnet_per_machine) as machines:
314
315 bootstrap_manager.client.use_reserved_spaces(required_spaces)
316
317 base_machine = machines[0]["hostname"]
318
319 log.info("About to bootstrap.")
320 bootstrap_and_test(
321 bootstrap_manager, bundle_path, base_machine)
322
323
324def parse_args(argv):
325 """Parse all arguments."""
326 parser = argparse.ArgumentParser(description="assess endpoint bindings")
327 add_basic_testing_arguments(parser)
328 args = parser.parse_args(argv)
329 if args.upload_tools:
330 parser.error("giving --upload-tools meaningless on 2.0 only test")
331 return args
332
333
334def main(argv=None):
335 args = parse_args(argv)
336 configure_logging(args.verbose)
337 bs_manager = BootstrapManager.from_args(args)
338 with maas_account_from_config(bs_manager.client.env.config) as account:
339 assess_endpoint_bindings(account, bs_manager)
340 return 0
341
342
343if __name__ == '__main__':
344 sys.exit(main())
0345
=== modified file 'jujupy.py'
--- jujupy.py 2016-10-21 19:50:45 +0000
+++ jujupy.py 2016-10-21 22:18:30 +0000
@@ -940,6 +940,9 @@
940940
941 controller_permissions = frozenset(['login', 'addmodel', 'superuser'])941 controller_permissions = frozenset(['login', 'addmodel', 'superuser'])
942942
943 reserved_spaces = frozenset([
944 'endpoint-bindings-data', 'endpoint-bindings-public'])
945
943 @classmethod946 @classmethod
944 def preferred_container(cls):947 def preferred_container(cls):
945 for container_type in [LXD_MACHINE, LXC_MACHINE]:948 for container_type in [LXD_MACHINE, LXC_MACHINE]:
@@ -1032,6 +1035,7 @@
1032 feature_flags = self.feature_flags.intersection(cls.used_feature_flags)1035 feature_flags = self.feature_flags.intersection(cls.used_feature_flags)
1033 backend = self._backend.clone(full_path, version, debug, feature_flags)1036 backend = self._backend.clone(full_path, version, debug, feature_flags)
1034 other = cls.from_backend(backend, env)1037 other = cls.from_backend(backend, env)
1038 other.excluded_spaces = set(self.excluded_spaces)
1035 return other1039 return other
10361040
1037 @classmethod1041 @classmethod
@@ -1108,6 +1112,7 @@
1108 env.juju_home = get_juju_home()1112 env.juju_home = get_juju_home()
1109 else:1113 else:
1110 env.juju_home = juju_home1114 env.juju_home = juju_home
1115 self.excluded_spaces = set(self.reserved_spaces)
11111116
1112 @property1117 @property
1113 def version(self):1118 def version(self):
@@ -1141,6 +1146,12 @@
1141 return self._backend.shell_environ(self.used_feature_flags,1146 return self._backend.shell_environ(self.used_feature_flags,
1142 self.env.juju_home)1147 self.env.juju_home)
11431148
1149 def use_reserved_spaces(self, spaces):
1150 """Allow machines in given spaces to be allocated and used."""
1151 if not self.reserved_spaces.issuperset(spaces):
1152 raise ValueError('Space not reserved: {}'.format(spaces))
1153 self.excluded_spaces.difference_update(spaces)
1154
1144 def add_ssh_machines(self, machines):1155 def add_ssh_machines(self, machines):
1145 for count, machine in enumerate(machines):1156 for count, machine in enumerate(machines):
1146 try:1157 try:
@@ -1163,11 +1174,7 @@
1163 credential=None, auto_upgrade=False, metadata_source=None,1174 credential=None, auto_upgrade=False, metadata_source=None,
1164 to=None, no_gui=False, agent_version=None):1175 to=None, no_gui=False, agent_version=None):
1165 """Return the bootstrap arguments for the substrate."""1176 """Return the bootstrap arguments for the substrate."""
1166 if self.env.joyent:1177 constraints = self._get_substrate_constraints()
1167 # Only accept kvm packages by requiring >1 cpu core, see lp:1446264
1168 constraints = 'mem=2G cpu-cores=1'
1169 else:
1170 constraints = 'mem=2G'
1171 cloud_region = self.get_cloud_region(self.env.get_cloud(),1178 cloud_region = self.get_cloud_region(self.env.get_cloud(),
1172 self.env.get_region())1179 self.env.get_region())
1173 # Note cloud_region before controller name1180 # Note cloud_region before controller name
@@ -1523,6 +1530,10 @@
1523 if self.env.joyent:1530 if self.env.joyent:
1524 # Only accept kvm packages by requiring >1 cpu core, see lp:14462641531 # Only accept kvm packages by requiring >1 cpu core, see lp:1446264
1525 return 'mem=2G cpu-cores=1'1532 return 'mem=2G cpu-cores=1'
1533 elif self.env.maas:
1534 # For now only maas support spaces in a meaningful way.
1535 return 'mem=2G spaces={}'.format( ','.join(
1536 '^' + space for space in sorted(self.excluded_spaces)))
1526 else:1537 else:
1527 return 'mem=2G'1538 return 'mem=2G'
15281539
@@ -2559,6 +2570,13 @@
2559 else:2570 else:
2560 return unqualified_model_name(self.model_name)2571 return unqualified_model_name(self.model_name)
25612572
2573 def _get_substrate_constraints(self):
2574 if self.env.joyent:
2575 # Only accept kvm packages by requiring >1 cpu core, see lp:1446264
2576 return 'mem=2G cpu-cores=1'
2577 else:
2578 return 'mem=2G'
2579
2562 def get_bootstrap_args(self, upload_tools, bootstrap_series=None,2580 def get_bootstrap_args(self, upload_tools, bootstrap_series=None,
2563 credential=None):2581 credential=None):
2564 """Return the bootstrap arguments for the substrate."""2582 """Return the bootstrap arguments for the substrate."""
25652583
=== modified file 'substrate.py'
--- substrate.py 2016-09-28 12:51:00 +0000
+++ substrate.py 2016-10-21 22:18:30 +0000
@@ -440,6 +440,8 @@
440440
441 _API_PATH = 'api/2.0/'441 _API_PATH = 'api/2.0/'
442442
443 STATUS_READY = 4
444
443 SUBNET_CONNECTION_MODES = frozenset(('AUTO', 'DHCP', 'STATIC', 'LINK_UP'))445 SUBNET_CONNECTION_MODES = frozenset(('AUTO', 'DHCP', 'STATIC', 'LINK_UP'))
444446
445 def __init__(self, profile, url, oauth):447 def __init__(self, profile, url, oauth):
@@ -493,6 +495,10 @@
493 if v['ip_addresses']}495 if v['ip_addresses']}
494 return ips496 return ips
495497
498 def machines(self):
499 """Return list of all machines."""
500 return self._maas(self.profile, 'machines', 'read')
501
496 def fabrics(self):502 def fabrics(self):
497 """Return list of all fabrics."""503 """Return list of all fabrics."""
498 return self._maas(self.profile, 'fabrics', 'read')504 return self._maas(self.profile, 'fabrics', 'read')
@@ -538,6 +544,22 @@
538 """Return list of interfaces belonging to node with given system_id."""544 """Return list of interfaces belonging to node with given system_id."""
539 return self._maas(self.profile, 'interfaces', 'read', system_id)545 return self._maas(self.profile, 'interfaces', 'read', system_id)
540546
547 def interface_update(self, system_id, interface_id, name=None,
548 mac_address=None, tags=None, vlan_id=None):
549 """Update fields of existing interface on node with given system_id."""
550 args = [
551 self.profile, 'interface', 'update', system_id, str(interface_id),
552 ]
553 if name is not None:
554 args.append('name=' + name)
555 if mac_address is not None:
556 args.append('mac_address=' + mac_address)
557 if tags is not None:
558 args.append('tags=' + tags)
559 if vlan_id is not None:
560 args.append('vlan=' + str(vlan_id))
561 return self._maas(*args)
562
541 def interface_create_vlan(self, system_id, parent, vlan_id):563 def interface_create_vlan(self, system_id, parent, vlan_id):
542 """Create a vlan interface on machine with given system_id."""564 """Create a vlan interface on machine with given system_id."""
543 args = [565 args = [
544566
=== added file 'tests/test_assess_endpoint_bindings.py'
--- tests/test_assess_endpoint_bindings.py 1970-01-01 00:00:00 +0000
+++ tests/test_assess_endpoint_bindings.py 2016-10-21 22:18:30 +0000
@@ -0,0 +1,177 @@
1"""Tests for assess_endpoint_bindings module."""
2
3import logging
4from mock import Mock, patch
5import StringIO
6
7from assess_endpoint_bindings import (
8 assess_endpoint_bindings,
9 ensure_spaces,
10 parse_args,
11 machine_spaces_for_bundle,
12 main,
13)
14from tests import (
15 parse_error,
16 TestCase,
17)
18from tests.test_jujupy import fake_juju_client
19
20
21class TestParseArgs(TestCase):
22
23 def test_common_args(self):
24 args = parse_args(["an-env", "/bin/juju", "/tmp/logs", "an-env-mod"])
25 self.assertEqual("an-env", args.env)
26 self.assertEqual("/bin/juju", args.juju_bin)
27 self.assertEqual("/tmp/logs", args.logs)
28 self.assertEqual("an-env-mod", args.temp_env_name)
29 self.assertEqual(False, args.debug)
30
31 def test_no_upload_tools(self):
32 with parse_error(self) as fake_stderr:
33 parse_args(["an-env", "/bin/juju", "--upload-tools"])
34 self.assertIn(
35 "error: giving --upload-tools meaningless on 2.0 only test",
36 fake_stderr.getvalue())
37
38 def test_help(self):
39 fake_stdout = StringIO.StringIO()
40 with parse_error(self) as fake_stderr:
41 with patch("sys.stdout", fake_stdout):
42 parse_args(["--help"])
43 self.assertEqual("", fake_stderr.getvalue())
44 self.assertIn("endpoint bindings", fake_stdout.getvalue())
45
46
47class TestEnsureSpaces(TestCase):
48
49 default_space = {
50 "name": "Default space",
51 "id": 0,
52 "resource_uri": "/MAAS/api/2.0/spaces/0/",
53 "subnets": [
54 {
55 "space": "Default space",
56 "id": 2,
57 },
58 ],
59 }
60 alpha_space = {
61 "name": "alpha-space",
62 "id": 60,
63 "resource_uri": "/MAAS/api/2.0/spaces/60/",
64 "subnets": [],
65 }
66 beta_space = {
67 "name": "beta-space",
68 "id": 61,
69 "resource_uri": "/MAAS/api/2.0/spaces/61/",
70 "subnets": [],
71 }
72
73 def test_all_existing(self):
74 manager = Mock(spec=["spaces"])
75 manager.spaces.return_value = [self.default_space, self.alpha_space]
76 spaces = ensure_spaces(manager, ["alpha-space"])
77 self.assertEqual(spaces, [self.alpha_space])
78 manager.spaces.assert_called_once_with()
79 self.assertEqual(
80 "INFO Have spaces: Default space, alpha-space\n",
81 self.log_stream.getvalue())
82
83 def test_some_existing(self):
84 manager = Mock(spec=["create_space", "spaces"])
85 manager.create_space.return_value = self.alpha_space
86 manager.spaces.return_value = [self.default_space, self.beta_space]
87 spaces = ensure_spaces(manager, ["alpha-space", "beta-space"])
88 self.assertEqual(spaces, [self.alpha_space, self.beta_space])
89 manager.spaces.assert_called_once_with()
90 manager.create_space.assert_called_once_with("alpha-space")
91 self.assertRegexpMatches(
92 self.log_stream.getvalue(),
93 r"^INFO Have spaces: Default space, beta-space\n"
94 r"INFO Created space: \{.*\}\n$")
95
96
97class TestMachineSpacesForBundle(TestCase):
98
99 def test_no_bindings(self):
100 bundle_without_bindings = {
101 "services": {
102 "ubuntu": {
103 "charm": "cs:ubuntu",
104 "num_units": 3,
105 },
106 },
107 }
108 machines = machine_spaces_for_bundle(bundle_without_bindings)
109 self.assertEqual(machines, [frozenset(), frozenset(), frozenset()])
110
111 def test_single_binding(self):
112 bundle_without_bindings = {
113 "services": {
114 "anapp": {
115 "charm": "./anapp",
116 "series": "xenial",
117 "num_units": 1,
118 "bindings": {
119 "website": "space-public",
120 },
121 },
122 },
123 }
124 machines = machine_spaces_for_bundle(bundle_without_bindings)
125 self.assertEqual(machines, [frozenset(["space-public"])])
126
127 def test_multiple_bindings(self):
128 bundle_without_bindings = {
129 "services": {
130 "anapp": {
131 "charm": "./anapp",
132 "series": "xenial",
133 "num_units": 1,
134 "bindings": {
135 "website": "space-public",
136 "data": "space-data",
137 "monitoring": "space-ctl",
138 },
139 },
140 "adb": {
141 "charm": "./adb",
142 "series": "xenial",
143 "num_units": 2,
144 "bindings": {
145 "data": "space-data",
146 "monitoring": "space-ctl",
147 },
148 },
149 },
150 }
151 machines = machine_spaces_for_bundle(bundle_without_bindings)
152 app_spaces = frozenset(["space-data", "space-ctl", "space-public"])
153 db_spaces = frozenset(["space-data", "space-ctl"])
154 self.assertEqual(machines, [app_spaces, db_spaces, db_spaces])
155
156
157class TestMain(TestCase):
158
159 def test_main(self):
160 argv = ["an-env", "/bin/juju", "/tmp/logs", "an-env-mod", "--verbose"]
161 env = Mock(spec=["config"])
162 client = Mock(spec=["env", "is_jes_enabled"])
163 client.env = env
164 with patch("assess_endpoint_bindings.configure_logging",
165 autospec=True) as mock_cl:
166 with patch("assess_endpoint_bindings.maas_account_from_config",
167 autospec=True) as mock_ma:
168 with patch("deploy_stack.client_from_config",
169 return_value=client) as mock_c:
170 with patch("assess_endpoint_bindings.assess_endpoint_bindings",
171 autospec=True) as mock_assess:
172 main(argv)
173 mock_cl.assert_called_once_with(logging.DEBUG)
174 mock_c.assert_called_once_with(
175 "an-env", "/bin/juju", debug=False, soft_deadline=None)
176 self.assertEqual(mock_ma.call_count, 1)
177 self.assertEqual(mock_assess.call_count, 1)
0178
=== modified file 'tests/test_jujupy.py'
--- tests/test_jujupy.py 2016-10-21 20:02:20 +0000
+++ tests/test_jujupy.py 2016-10-21 22:18:30 +0000
@@ -796,7 +796,8 @@
796 client.bootstrap()796 client.bootstrap()
797 mock.assert_called_with(797 mock.assert_called_with(
798 'bootstrap', (798 'bootstrap', (
799 '--constraints', 'mem=2G',799 '--constraints', 'mem=2G spaces=^endpoint_bindings_data,'
800 '^endpoint_bindings_public',
800 'foo/asdf', 'maas',801 'foo/asdf', 'maas',
801 '--config', config_file.name, '--default-model', 'maas',802 '--config', config_file.name, '--default-model', 'maas',
802 '--agent-version', '2.0'),803 '--agent-version', '2.0'),
803804
=== modified file 'tests/test_substrate.py'
--- tests/test_substrate.py 2016-10-14 19:07:39 +0000
+++ tests/test_substrate.py 2016-10-21 22:18:30 +0000
@@ -877,6 +877,14 @@
877 ('maas', 'mas', 'machines', 'list-allocated'))877 ('maas', 'mas', 'machines', 'list-allocated'))
878 self.assertEqual({}, ips)878 self.assertEqual({}, ips)
879879
880 def test_machines(self):
881 account = self.get_account()
882 with patch('subprocess.check_output', autospec=True,
883 return_value='[]') as co_mock:
884 machines = account.machines()
885 co_mock.assert_called_once_with(('maas', 'mas', 'machines', 'read'))
886 self.assertEqual([], machines)
887
880 def test_fabrics(self):888 def test_fabrics(self):
881 account = self.get_account()889 account = self.get_account()
882 with patch('subprocess.check_output', autospec=True,890 with patch('subprocess.check_output', autospec=True,
@@ -967,6 +975,16 @@
967 'maas', 'mas', 'interfaces', 'read', 'node-xyz'))975 'maas', 'mas', 'interfaces', 'read', 'node-xyz'))
968 self.assertEqual([], interfaces)976 self.assertEqual([], interfaces)
969977
978 def test_interface_update(self):
979 account = self.get_account()
980 with patch('subprocess.check_output', autospec=True,
981 return_value='{"id": 10}') as co_mock:
982 interface = account.interface_update('node-xyz', 10, vlan=5000)
983 co_mock.assert_called_once_with((
984 'maas', 'mas', 'interface', 'update', 'node-xyz', '10',
985 'vlan=5000'))
986 self.assertEqual({'id': 10}, interface)
987
970 def test_interface_create_vlan(self):988 def test_interface_create_vlan(self):
971 account = self.get_account()989 account = self.get_account()
972 with patch('subprocess.check_output', autospec=True,990 with patch('subprocess.check_output', autospec=True,

Subscribers

People subscribed via source and target branches