From 0d70179f2088da28e47bcad569366fb3dddd6e6d Mon Sep 17 00:00:00 2001 From: Pete Vander Giessen Date: Fri, 16 Dec 2016 14:41:04 -0500 Subject: [PATCH] Fixes for landscape bundle. Parses constraints in some places where we weren't parsing them. Normalizes keys in machine params. Adds support for Placements on an lxd container *or* a machine. Substitutes lxd containers for lxc containers. --- juju/application.py | 2 +- juju/constraints.py | 14 ++++++++++++++ juju/model.py | 18 ++++++++++++++---- juju/placement.py | 18 ++++++++++++++---- tests/unit/test_constraints.py | 4 +++- tests/unit/test_placement.py | 8 ++++---- 6 files changed, 50 insertions(+), 14 deletions(-) diff --git a/juju/application.py b/juju/application.py index 69f412f..55b9d1e 100644 --- a/juju/application.py +++ b/juju/application.py @@ -88,7 +88,7 @@ class Application(model.ModelEntity): result = await app_facade.AddUnits( application=self.name, - placement=[parse_placement(to)] if to else None, + placement=parse_placement(to) if to else None, num_units=count, ) diff --git a/juju/constraints.py b/juju/constraints.py index 43c048a..97529e4 100644 --- a/juju/constraints.py +++ b/juju/constraints.py @@ -28,6 +28,9 @@ FACTORS = { "P": 1024 * 1024 * 1024 } +SNAKE1 = re.compile(r'(.)([A-Z][a-z]+)') +SNAKE2 = re.compile('([a-z0-9])([A-Z])') + def parse(constraints): """ Constraints must be expressed as a string containing only spaces @@ -37,6 +40,9 @@ def parse(constraints): if constraints is None: return None + if constraints == "": + return None + if type(constraints) is dict: # Fowards compatibilty: already parsed return constraints @@ -52,6 +58,11 @@ def normalize_key(key): key = key.strip() key = key.replace("-", "_") # Our _client lib wants "_" in place of "-" + + # Convert camelCase to snake_case + key = SNAKE1.sub(r'\1_\2', key) + key = SNAKE2.sub(r'\1_\2', key).lower() + return key @@ -68,4 +79,7 @@ def normalize_value(value): values = [normalize_value(v) for v in values] return values + if value.isdigit(): + return int(value) + return value diff --git a/juju/model.py b/juju/model.py index 7897d42..4a8bc03 100644 --- a/juju/model.py +++ b/juju/model.py @@ -14,7 +14,7 @@ from theblues import charmstore from .client import client from .client import watcher from .client import connection -from .constraints import parse as parse_constraints +from .constraints import parse as parse_constraints, normalize_key from .delta import get_entity_delta from .delta import get_entity_class from .exceptions import DeadEntityException @@ -914,7 +914,7 @@ class Model(object): channel=channel, charm_url=entity_id, config=config, - constraints=constraints, + constraints=parse_constraints(constraints), endpoint_bindings=bind, num_units=num_units, placement=placement, @@ -1364,7 +1364,7 @@ class BundleHandler(object): expects. container_type: string holding the type of the container (for - instance ""lxc" or kvm"). It is not specified for top level + instance ""lxd" or kvm"). It is not specified for top level machines. parent_id: string holding a placeholder pointing to another @@ -1375,6 +1375,10 @@ class BundleHandler(object): """ params = params or {} + # Normalize keys + params = {normalize_key(k): params[k] for k in params.keys()} + + # Fix up values, as necessary. if 'parent_id' in params: params['parent_id'] = self.resolve(params['parent_id']) @@ -1382,6 +1386,12 @@ class BundleHandler(object): params.get('constraints')) params['jobs'] = params.get('jobs', ['JobHostUnits']) + if params.get('container_type') == 'lxc': + log.warning('Juju 2.0 does not support lxc containers. ' + 'Converting containers to lxd.') + params['container_type'] = 'lxd' + + # Submit the request. params = client.AddMachineParams(**params) results = await self.client_facade.AddMachines([params]) error = results.machines[0].error @@ -1450,7 +1460,7 @@ class BundleHandler(object): series=series, application=application, config=options, - constraints=constraints, + constraints=parse_constraints(constraints), storage=storage, endpoint_bindings=endpoint_bindings, resources=resources, diff --git a/juju/placement.py b/juju/placement.py index 6f91aed..561bc40 100644 --- a/juju/placement.py +++ b/juju/placement.py @@ -25,18 +25,28 @@ def parse(directive): if type(directive) in [dict, client.Placement]: # We've been handed something that we can simply hand back to # the api. (Forwards compatibility) - return directive + return [directive] + + # Juju 2.0 can't handle lxc containers. + directive = directive.replace('lxc', 'lxd') if ":" in directive: # Planner has given us a scope and directive in string form scope, directive = directive.split(":") - return client.Placement(scope=scope, directive=directive) + return [client.Placement(scope=scope, directive=directive)] if directive.isdigit(): # Planner has given us a machine id (we rely on juju core to # verify its validity.) - return client.Placement(scope=MACHINE_SCOPE, directive=directive) + return [client.Placement(scope=MACHINE_SCOPE, directive=directive)] + + if "/" in directive: + machine, container, container_num = directive.split("/") + return [ + client.Placement(scope=MACHINE_SCOPE, directive=machine), + client.Placement(scope=container, directive=container_num) + ] # Planner has probably given us a container type. Leave it up to # juju core to verify that it is valid. - return client.Placement(scope=directive) + return [client.Placement(scope=directive)] diff --git a/tests/unit/test_constraints.py b/tests/unit/test_constraints.py index ec50bdd..cb9d773 100644 --- a/tests/unit/test_constraints.py +++ b/tests/unit/test_constraints.py @@ -22,13 +22,15 @@ class TestConstraints(unittest.TestCase): self.assertEqual(_("test-key"), "test_key") self.assertEqual(_("test-key "), "test_key") self.assertEqual(_(" test-key"), "test_key") + self.assertEqual(_("TestKey"), "test_key") + self.assertEqual(_("testKey"), "test_key") def test_normalize_val(self): _ = constraints.normalize_value self.assertEqual(_("10G"), 10 * 1024) self.assertEqual(_("10M"), 10) - self.assertEqual(_("10"), "10") + self.assertEqual(_("10"), 10) self.assertEqual(_("foo,bar"), ["foo", "bar"]) def test_parse_constraints(self): diff --git a/tests/unit/test_placement.py b/tests/unit/test_placement.py index 793266d..a78a28d 100644 --- a/tests/unit/test_placement.py +++ b/tests/unit/test_placement.py @@ -11,10 +11,10 @@ class TestPlacement(unittest.TestCase): def test_parse_both_specified(self): res = placement.parse("foo:bar") - self.assertEqual(res.scope, "foo") - self.assertEqual(res.directive, "bar") + self.assertEqual(res[0].scope, "foo") + self.assertEqual(res[0].directive, "bar") def test_parse_machine(self): res = placement.parse("22") - self.assertEqual(res.scope, "#") - self.assertEqual(res.directive, "22") + self.assertEqual(res[0].scope, "#") + self.assertEqual(res[0].directive, "22") -- 2.25.1