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.
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,
)
"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
if constraints is None:
return None
+ if constraints == "":
+ return None
+
if type(constraints) is dict:
# Fowards compatibilty: already parsed
return constraints
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
values = [normalize_value(v) for v in values]
return values
+ if value.isdigit():
+ return int(value)
+
return value
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
channel=channel,
charm_url=entity_id,
config=config,
- constraints=constraints,
+ constraints=parse_constraints(constraints),
endpoint_bindings=bind,
num_units=num_units,
placement=placement,
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
"""
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'])
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
series=series,
application=application,
config=options,
- constraints=constraints,
+ constraints=parse_constraints(constraints),
storage=storage,
endpoint_bindings=endpoint_bindings,
resources=resources,
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)]
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):
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")