X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=juju%2Fapplication.py;h=8719a629d2153b63374a3959b70f29c89d825793;hb=c50c361a8b9a3bbf1a33f5659e492b481f065cd2;hp=c3d2f0d643310d09f5d11911ae31cfb06b5f1d0c;hpb=4c1f68ce9871fa965fe9683aeeb5ddd66b42a5bc;p=osm%2FN2VC.git diff --git a/juju/application.py b/juju/application.py index c3d2f0d..8719a62 100644 --- a/juju/application.py +++ b/juju/application.py @@ -3,6 +3,8 @@ import logging from . import model from .client import client +from .errors import JujuError +from .placement import parse as parse_placement log = logging.getLogger(__name__) @@ -35,7 +37,25 @@ class Application(model.ModelEntity): if unit.application == self.name ] - def add_relation(self, local_relation, remote_relation): + @property + def status(self): + """Get the application status, as set by the charm's leader. + + """ + return self.safe_data['status']['current'] + + @property + def status_message(self): + """Get the application status message, as set by the charm's leader. + + """ + return self.safe_data['status']['message'] + + @property + def tag(self): + return 'application-%s' % self.name + + async def add_relation(self, local_relation, remote_relation): """Add a relation to another application. :param str local_relation: Name of relation on this application @@ -43,7 +63,10 @@ class Application(model.ModelEntity): application in the form '[:]' """ - pass + if ':' not in local_relation: + local_relation = '{}:{}'.format(self.name, local_relation) + + return await self.model.add_relation(local_relation, remote_relation) async def add_unit(self, count=1, to=None): """Add one or more units to this application. @@ -57,8 +80,7 @@ class Application(model.ModelEntity): If None, a new machine is provisioned. """ - app_facade = client.ApplicationFacade() - app_facade.connect(self.connection) + app_facade = client.ApplicationFacade.from_connection(self.connection) log.debug( 'Adding %s unit%s to %s', @@ -66,7 +88,7 @@ class Application(model.ModelEntity): result = await app_facade.AddUnits( application=self.name, - placement=to, + placement=parse_placement(to) if to else None, num_units=count, ) @@ -84,7 +106,7 @@ class Application(model.ModelEntity): :param int value: Budget limit """ - pass + raise NotImplementedError() def attach(self, resource_name, file_path): """Upload a file as a resource for this application. @@ -93,15 +115,15 @@ class Application(model.ModelEntity): :param str file_path: Path to the file to upload """ - pass + raise NotImplementedError() def collect_metrics(self): """Collect metrics on this application. """ - pass + raise NotImplementedError() - def destroy_relation(self, local_relation, remote_relation): + async def destroy_relation(self, local_relation, remote_relation): """Remove a relation to another application. :param str local_relation: Name of relation on this application @@ -109,15 +131,30 @@ class Application(model.ModelEntity): application in the form '[:]' """ - pass + if ':' not in local_relation: + local_relation = '{}:{}'.format(self.name, local_relation) + + app_facade = client.ApplicationFacade.from_connection(self.connection) + + log.debug( + 'Destroying relation %s <-> %s', local_relation, remote_relation) + + return await app_facade.DestroyRelation([ + local_relation, remote_relation]) remove_relation = destroy_relation + async def destroy_unit(self, *unit_names): + """Destroy units by name. + + """ + return await self.model.destroy_units(*unit_names) + destroy_units = destroy_unit + async def destroy(self): """Remove this application from the model. """ - app_facade = client.ApplicationFacade() - app_facade.connect(self.connection) + app_facade = client.ApplicationFacade.from_connection(self.connection) log.debug( 'Destroying %s', self.name) @@ -129,25 +166,35 @@ class Application(model.ModelEntity): """Make this application publicly available over the network. """ - app_facade = client.ApplicationFacade() - app_facade.connect(self.connection) + app_facade = client.ApplicationFacade.from_connection(self.connection) log.debug( 'Exposing %s', self.name) return await app_facade.Expose(self.name) - def get_config(self): - """Return the configuration settings for this application. + async def get_config(self): + """Return the configuration settings dict for this application. """ - pass + app_facade = client.ApplicationFacade.from_connection(self.connection) - def get_constraints(self): - """Return the machine constraints for this application. + log.debug( + 'Getting config for %s', self.name) + + return (await app_facade.Get(self.name)).config + + async def get_constraints(self): + """Return the machine constraints dict for this application. """ - pass + app_facade = client.ApplicationFacade.from_connection(self.connection) + + log.debug( + 'Getting constraints for %s', self.name) + + result = (await app_facade.Get(self.name)).constraints + return vars(result) if result else result def get_actions(self, schema=False): """Get actions defined for this application. @@ -155,7 +202,7 @@ class Application(model.ModelEntity): :param bool schema: Return the full action schema """ - pass + raise NotImplementedError() def get_resources(self, details=False): """Return resources for this application. @@ -164,16 +211,28 @@ class Application(model.ModelEntity): unit """ - pass + raise NotImplementedError() - def run(self, command, timeout=None): + async def run(self, command, timeout=None): """Run command on all units for this application. :param str command: The command to run :param int timeout: Time to wait before command is considered failed """ - pass + action = client.ActionFacade.from_connection(self.connection) + + log.debug( + 'Running `%s` on all units of %s', command, self.name) + + # TODO this should return a list of Actions + return await action.Run( + [self.name], + command, + [], + timeout, + [], + ) async def set_annotations(self, annotations): """Set annotations on this application. @@ -184,31 +243,41 @@ class Application(model.ModelEntity): """ log.debug('Updating annotations on application %s', self.name) - self.ann_facade = client.AnnotationsFacade() - self.ann_facade.connect(model.connection) + self.ann_facade = client.AnnotationsFacade.from_connection( + self.connection) ann = client.EntityAnnotations( - entity=self.name, + entity=self.tag, annotations=annotations, ) return await self.ann_facade.Set([ann]) - def set_config(self, to_default=False, **config): + async def set_config(self, config, to_default=False): """Set configuration options for this application. + :param config: Dict of configuration to set :param bool to_default: Set application options to default values - :param \*\*config: Config key/values """ - pass + app_facade = client.ApplicationFacade.from_connection(self.connection) + + log.debug( + 'Setting config for %s: %s', self.name, config) - def set_constraints(self, constraints): + return await app_facade.Set(self.name, config) + + async def set_constraints(self, constraints): """Set machine constraints for this application. - :param :class:`juju.Constraints` constraints: Machine constraints + :param dict constraints: Dict of machine constraints """ - pass + app_facade = client.ApplicationFacade.from_connection(self.connection) + + log.debug( + 'Setting constraints for %s: %s', self.name, constraints) + + return await app_facade.SetConstraints(self.name, constraints) def set_meter_status(self, status, info=None): """Set the meter status on this status. @@ -217,7 +286,7 @@ class Application(model.ModelEntity): :param str info: Extra info message """ - pass + raise NotImplementedError() def set_plan(self, plan_name): """Set the plan for this application, effective immediately. @@ -225,13 +294,18 @@ class Application(model.ModelEntity): :param str plan_name: Name of plan """ - pass + raise NotImplementedError() - def unexpose(self): + async def unexpose(self): """Remove public availability over the network for this application. """ - pass + app_facade = client.ApplicationFacade.from_connection(self.connection) + + log.debug( + 'Unexposing %s', self.name) + + return await app_facade.Unexpose(self.name) def update_allocation(self, allocation): """Update existing allocation for this application. @@ -239,11 +313,11 @@ class Application(model.ModelEntity): :param int allocation: The allocation to set """ - pass + raise NotImplementedError() - def upgrade_charm( + async def upgrade_charm( self, channel=None, force_series=False, force_units=False, - path=None, resources=None, revision=-1, switch=None): + path=None, resources=None, revision=None, switch=None): """Upgrade the charm for this application. :param str channel: Channel to use when getting the charm from the @@ -258,4 +332,60 @@ class Application(model.ModelEntity): :param str switch: Crossgrade charm url """ - pass + # TODO: Support local upgrades + if path is not None: + raise NotImplementedError("path option is not implemented") + if resources is not None: + raise NotImplementedError("resources option is not implemented") + + if switch is not None and revision is not None: + raise ValueError("switch and revision are mutually exclusive") + + client_facade = client.ClientFacade.from_connection(self.connection) + app_facade = client.ApplicationFacade.from_connection(self.connection) + + if switch is not None: + charm_url = switch + if not charm_url.startswith('cs:'): + charm_url = 'cs:' + charm_url + else: + charm_url = self.data['charm-url'] + charm_url = charm_url.rpartition('-')[0] + if revision is not None: + charm_url = "%s-%d" % (charm_url, revision) + else: + charmstore = self.model.charmstore + entity = await charmstore.entity(charm_url, channel=channel) + charm_url = entity['Id'] + + if charm_url == self.data['charm-url']: + raise JujuError('already running charm "%s"' % charm_url) + + await client_facade.AddCharm( + url=charm_url, + channel=channel + ) + + await app_facade.SetCharm( + application=self.entity_id, + channel=channel, + charm_url=charm_url, + config_settings=None, + config_settings_yaml=None, + force_series=force_series, + force_units=force_units, + resource_ids=None, + storage_constraints=None + ) + + await self.model.block_until( + lambda: self.data['charm-url'] == charm_url + ) + + async def get_metrics(self): + """Get metrics for this application's units. + + :return: Dictionary of unit_name:metrics + + """ + return await self.model.get_metrics(self.tag)