X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=juju%2Fmodel.py;h=ecd764bf20d049123ce54756e50b713f7e1debfd;hb=1ad381e8757ec6647111adbff4985be257368da9;hp=7464e2cc7be3898e322e5a07379170f939c3bf30;hpb=e2e8a6abd629c0941abb8b80ef75df2710ed952c;p=osm%2FN2VC.git diff --git a/juju/model.py b/juju/model.py index 7464e2c..ecd764b 100644 --- a/juju/model.py +++ b/juju/model.py @@ -14,11 +14,11 @@ 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 -from .errors import JujuAPIError +from .errors import JujuError, JujuAPIError log = logging.getLogger(__name__) @@ -74,6 +74,14 @@ class ModelObserver(object): await method(delta, old, new, model) async def on_change(self, delta, old, new, model): + """Generic model-change handler. + + :param delta: :class:`juju.client.overrides.Delta` + :param old: :class:`juju.model.ModelEntity` + :param new: :class:`juju.model.ModelEntity` + :param model: :class:`juju.model.Model` + + """ pass @@ -207,18 +215,16 @@ class ModelEntity(object): self.connected = connected self.connection = model.connection + def __repr__(self): + return '<{} entity_id="{}">'.format(type(self).__name__, + self.entity_id) + def __getattr__(self, name): """Fetch object attributes from the underlying data dict held in the model. """ - if self.data is None: - raise DeadEntityException( - "Entity {}:{} is dead - its attributes can no longer be " - "accessed. Use the .previous() method on this object to get " - "a copy of the object at its previous state.".format( - self.entity_type, self.entity_id)) - return self.data[name] + return self.safe_data[name] def __bool__(self): return bool(self.data) @@ -285,6 +291,22 @@ class ModelEntity(object): return self.model.state.entity_data( self.entity_type, self.entity_id, self._history_index) + @property + def safe_data(self): + """The data dictionary for this entity. + + If this `ModelEntity` points to the dead state, it will + raise `DeadEntityException`. + + """ + if self.data is None: + raise DeadEntityException( + "Entity {}:{} is dead - its attributes can no longer be " + "accessed. Use the .previous() method on this object to get " + "a copy of the object at its previous state.".format( + self.entity_type, self.entity_id)) + return self.data + def previous(self): """Return a copy of this object as was at its previous state in history. @@ -425,13 +447,13 @@ class Model(object): lambda: len(self.machines) == 0 ) - async def block_until(self, *conditions, timeout=None): + async def block_until(self, *conditions, timeout=None, wait_period=0.5): """Return only after all conditions are true. """ async def _block(): while not all(c() for c in conditions): - await asyncio.sleep(0) + await asyncio.sleep(wait_period) await asyncio.wait_for(_block(), timeout) @property @@ -809,14 +831,14 @@ class Model(object): pass async def deploy( - self, entity_url, service_name=None, bind=None, budget=None, + self, entity_url, application_name=None, bind=None, budget=None, channel=None, config=None, constraints=None, force=False, num_units=1, plan=None, resources=None, series=None, storage=None, to=None): """Deploy a new service or bundle. :param str entity_url: Charm or bundle url - :param str service_name: Name to give the service + :param str application_name: Name to give the service :param dict bind: : pairs :param dict budget: : pairs :param str channel: Charm store channel from which to retrieve @@ -842,7 +864,7 @@ class Model(object): TODO:: - - service_name is required; fill this in automatically if not + - application_name is required; fill this in automatically if not provided by caller - series is required; how do we pick a default? @@ -885,7 +907,7 @@ class Model(object): # haven't made it yet we'll need to wait on them to be added await asyncio.gather(*[ asyncio.ensure_future( - self.model._wait_for_new('application', app_name)) + self._wait_for_new('application', app_name)) for app_name in pending_apps ]) return [app for name, app in self.applications.items() @@ -896,11 +918,11 @@ class Model(object): await client_facade.AddCharm(channel, entity_id) app = client.ApplicationDeploy( - application=service_name, + application=application_name, channel=channel, charm_url=entity_id, config=config, - constraints=constraints, + constraints=parse_constraints(constraints), endpoint_bindings=bind, num_units=num_units, placement=placement, @@ -910,7 +932,7 @@ class Model(object): ) await app_facade.Deploy([app]) - return await self._wait_for_new('application', service_name) + return await self._wait_for_new('application', application_name) def destroy(self): """Terminate all machines and resources for this model. @@ -1250,6 +1272,8 @@ class Model(object): :param str \*tags: Tags of entities from which to retrieve metrics. No tags retrieves the metrics of all units in the model. + :return: Dictionary of unit_name:metrics + """ log.debug("Retrieving metrics for %s", ', '.join(tags) if tags else "all units") @@ -1308,6 +1332,9 @@ class BundleHandler(object): self.bundle = yaml.safe_load(bundle_yaml) self.plan = await self.client_facade.GetBundleChanges(bundle_yaml) + if self.plan.errors: + raise JujuError('\n'.join(self.plan.errors)) + async def execute_plan(self): for step in self.plan.changes: method = getattr(self, step.method) @@ -1350,7 +1377,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 @@ -1361,6 +1388,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']) @@ -1368,6 +1399,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 @@ -1436,7 +1473,7 @@ class BundleHandler(object): series=series, application=application, config=options, - constraints=constraints, + constraints=parse_constraints(constraints), storage=storage, endpoint_bindings=endpoint_bindings, resources=resources,