github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/application/application.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package application contains api calls for functionality
     5  // related to deploying and managing applications and their
     6  // related charms.
     7  package application
     8  
     9  import (
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"gopkg.in/juju/charm.v6-unstable"
    13  	csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
    14  	goyaml "gopkg.in/yaml.v2"
    15  
    16  	"github.com/juju/juju/apiserver/common"
    17  	"github.com/juju/juju/apiserver/facade"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/instance"
    20  	jjj "github.com/juju/juju/juju"
    21  	"github.com/juju/juju/permission"
    22  	"github.com/juju/juju/state"
    23  	statestorage "github.com/juju/juju/state/storage"
    24  )
    25  
    26  var (
    27  	logger = loggo.GetLogger("juju.apiserver.application")
    28  
    29  	newStateStorage = statestorage.NewStorage
    30  )
    31  
    32  func init() {
    33  	common.RegisterStandardFacade("Application", 1, newAPI)
    34  
    35  	// Facade version 2 adds support for the ConfigSettings
    36  	// and StorageConstraints fields in SetCharm.
    37  	common.RegisterStandardFacade("Application", 2, newAPI)
    38  }
    39  
    40  // API implements the application interface and is the concrete
    41  // implementation of the api end point.
    42  type API struct {
    43  	backend    Backend
    44  	authorizer facade.Authorizer
    45  	check      BlockChecker
    46  
    47  	// TODO(axw) stateCharm only exists because I ran out
    48  	// of time unwinding all of the tendrils of state. We
    49  	// should pass a charm.Charm and charm.URL back into
    50  	// state wherever we pass in a state.Charm currently.
    51  	stateCharm func(Charm) *state.Charm
    52  }
    53  
    54  func newAPI(
    55  	st *state.State,
    56  	resources facade.Resources,
    57  	authorizer facade.Authorizer,
    58  ) (*API, error) {
    59  	backend := NewStateBackend(st)
    60  	blockChecker := common.NewBlockChecker(st)
    61  	stateCharm := CharmToStateCharm
    62  	return NewAPI(
    63  		backend,
    64  		authorizer,
    65  		blockChecker,
    66  		stateCharm,
    67  	)
    68  }
    69  
    70  // NewAPI returns a new application API facade.
    71  func NewAPI(
    72  	backend Backend,
    73  	authorizer facade.Authorizer,
    74  	blockChecker BlockChecker,
    75  	stateCharm func(Charm) *state.Charm,
    76  ) (*API, error) {
    77  	if !authorizer.AuthClient() {
    78  		return nil, common.ErrPerm
    79  	}
    80  	return &API{
    81  		backend:    backend,
    82  		authorizer: authorizer,
    83  		check:      blockChecker,
    84  		stateCharm: stateCharm,
    85  	}, nil
    86  }
    87  
    88  func (api *API) checkCanRead() error {
    89  	canRead, err := api.authorizer.HasPermission(permission.ReadAccess, api.backend.ModelTag())
    90  	if err != nil {
    91  		return errors.Trace(err)
    92  	}
    93  	if !canRead {
    94  		return common.ErrPerm
    95  	}
    96  	return nil
    97  }
    98  
    99  func (api *API) checkCanWrite() error {
   100  	canWrite, err := api.authorizer.HasPermission(permission.WriteAccess, api.backend.ModelTag())
   101  	if err != nil {
   102  		return errors.Trace(err)
   103  	}
   104  	if !canWrite {
   105  		return common.ErrPerm
   106  	}
   107  	return nil
   108  }
   109  
   110  // SetMetricCredentials sets credentials on the application.
   111  func (api *API) SetMetricCredentials(args params.ApplicationMetricCredentials) (params.ErrorResults, error) {
   112  	if err := api.checkCanWrite(); err != nil {
   113  		return params.ErrorResults{}, errors.Trace(err)
   114  	}
   115  	result := params.ErrorResults{
   116  		Results: make([]params.ErrorResult, len(args.Creds)),
   117  	}
   118  	if len(args.Creds) == 0 {
   119  		return result, nil
   120  	}
   121  	for i, a := range args.Creds {
   122  		application, err := api.backend.Application(a.ApplicationName)
   123  		if err != nil {
   124  			result.Results[i].Error = common.ServerError(err)
   125  			continue
   126  		}
   127  		err = application.SetMetricCredentials(a.MetricCredentials)
   128  		if err != nil {
   129  			result.Results[i].Error = common.ServerError(err)
   130  		}
   131  	}
   132  	return result, nil
   133  }
   134  
   135  // Deploy fetches the charms from the charm store and deploys them
   136  // using the specified placement directives.
   137  func (api *API) Deploy(args params.ApplicationsDeploy) (params.ErrorResults, error) {
   138  	if err := api.checkCanWrite(); err != nil {
   139  		return params.ErrorResults{}, errors.Trace(err)
   140  	}
   141  	result := params.ErrorResults{
   142  		Results: make([]params.ErrorResult, len(args.Applications)),
   143  	}
   144  	if err := api.check.ChangeAllowed(); err != nil {
   145  		return result, errors.Trace(err)
   146  	}
   147  	for i, arg := range args.Applications {
   148  		err := deployApplication(api.backend, api.stateCharm, arg)
   149  		result.Results[i].Error = common.ServerError(err)
   150  	}
   151  	return result, nil
   152  }
   153  
   154  // deployApplication fetches the charm from the charm store and deploys it.
   155  // The logic has been factored out into a common function which is called by
   156  // both the legacy API on the client facade, as well as the new application facade.
   157  func deployApplication(
   158  	backend Backend,
   159  	stateCharm func(Charm) *state.Charm,
   160  	args params.ApplicationDeploy,
   161  ) error {
   162  	curl, err := charm.ParseURL(args.CharmURL)
   163  	if err != nil {
   164  		return errors.Trace(err)
   165  	}
   166  	if curl.Revision < 0 {
   167  		return errors.Errorf("charm url must include revision")
   168  	}
   169  
   170  	// Do a quick but not complete validation check before going any further.
   171  	for _, p := range args.Placement {
   172  		if p.Scope != instance.MachineScope {
   173  			continue
   174  		}
   175  		_, err = backend.Machine(p.Directive)
   176  		if err != nil {
   177  			return errors.Annotatef(err, `cannot deploy "%v" to machine %v`, args.ApplicationName, p.Directive)
   178  		}
   179  	}
   180  
   181  	// Try to find the charm URL in state first.
   182  	ch, err := backend.Charm(curl)
   183  	if err != nil {
   184  		return errors.Trace(err)
   185  	}
   186  
   187  	if err := checkMinVersion(ch); err != nil {
   188  		return errors.Trace(err)
   189  	}
   190  
   191  	var settings charm.Settings
   192  	if len(args.ConfigYAML) > 0 {
   193  		settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ApplicationName)
   194  	} else if len(args.Config) > 0 {
   195  		// Parse config in a compatible way (see function comment).
   196  		settings, err = parseSettingsCompatible(ch.Config(), args.Config)
   197  	}
   198  	if err != nil {
   199  		return errors.Trace(err)
   200  	}
   201  
   202  	channel := csparams.Channel(args.Channel)
   203  
   204  	_, err = jjj.DeployApplication(backend,
   205  		jjj.DeployApplicationParams{
   206  			ApplicationName:  args.ApplicationName,
   207  			Series:           args.Series,
   208  			Charm:            stateCharm(ch),
   209  			Channel:          channel,
   210  			NumUnits:         args.NumUnits,
   211  			ConfigSettings:   settings,
   212  			Constraints:      args.Constraints,
   213  			Placement:        args.Placement,
   214  			Storage:          args.Storage,
   215  			EndpointBindings: args.EndpointBindings,
   216  			Resources:        args.Resources,
   217  		})
   218  	return errors.Trace(err)
   219  }
   220  
   221  // ApplicationSetSettingsStrings updates the settings for the given application,
   222  // taking the configuration from a map of strings.
   223  func ApplicationSetSettingsStrings(application Application, settings map[string]string) error {
   224  	ch, _, err := application.Charm()
   225  	if err != nil {
   226  		return errors.Trace(err)
   227  	}
   228  	// Parse config in a compatible way (see function comment).
   229  	changes, err := parseSettingsCompatible(ch.Config(), settings)
   230  	if err != nil {
   231  		return errors.Trace(err)
   232  	}
   233  	return application.UpdateConfigSettings(changes)
   234  }
   235  
   236  // parseSettingsCompatible parses setting strings in a way that is
   237  // compatible with the behavior before this CL based on the issue
   238  // http://pad.lv/1194945. Until then setting an option to an empty
   239  // string caused it to reset to the default value. We now allow
   240  // empty strings as actual values, but we want to preserve the API
   241  // behavior.
   242  func parseSettingsCompatible(charmConfig *charm.Config, settings map[string]string) (charm.Settings, error) {
   243  	setSettings := map[string]string{}
   244  	unsetSettings := charm.Settings{}
   245  	// Split settings into those which set and those which unset a value.
   246  	for name, value := range settings {
   247  		if value == "" {
   248  			unsetSettings[name] = nil
   249  			continue
   250  		}
   251  		setSettings[name] = value
   252  	}
   253  	// Validate the settings.
   254  	changes, err := charmConfig.ParseSettingsStrings(setSettings)
   255  	if err != nil {
   256  		return nil, errors.Trace(err)
   257  	}
   258  	// Validate the unsettings and merge them into the changes.
   259  	unsetSettings, err = charmConfig.ValidateSettings(unsetSettings)
   260  	if err != nil {
   261  		return nil, errors.Trace(err)
   262  	}
   263  	for name := range unsetSettings {
   264  		changes[name] = nil
   265  	}
   266  	return changes, nil
   267  }
   268  
   269  // Update updates the application attributes, including charm URL,
   270  // minimum number of units, settings and constraints.
   271  // All parameters in params.ApplicationUpdate except the application name are optional.
   272  func (api *API) Update(args params.ApplicationUpdate) error {
   273  	if err := api.checkCanWrite(); err != nil {
   274  		return err
   275  	}
   276  	if !args.ForceCharmURL {
   277  		if err := api.check.ChangeAllowed(); err != nil {
   278  			return errors.Trace(err)
   279  		}
   280  	}
   281  	app, err := api.backend.Application(args.ApplicationName)
   282  	if err != nil {
   283  		return errors.Trace(err)
   284  	}
   285  	// Set the charm for the given application.
   286  	if args.CharmURL != "" {
   287  		// For now we do not support changing the channel through Update().
   288  		// TODO(ericsnow) Support it?
   289  		channel := app.Channel()
   290  		if err = api.applicationSetCharm(
   291  			args.ApplicationName,
   292  			app,
   293  			args.CharmURL,
   294  			channel,
   295  			nil, // charm settings (strings map)
   296  			"",  // charm settings (YAML)
   297  			args.ForceSeries,
   298  			args.ForceCharmURL,
   299  			nil, // resource IDs
   300  			nil, // storage constraints
   301  		); err != nil {
   302  			return errors.Trace(err)
   303  		}
   304  	}
   305  	// Set the minimum number of units for the given application.
   306  	if args.MinUnits != nil {
   307  		if err = app.SetMinUnits(*args.MinUnits); err != nil {
   308  			return errors.Trace(err)
   309  		}
   310  	}
   311  	// Set up application's settings.
   312  	if args.SettingsYAML != "" {
   313  		if err = applicationSetSettingsYAML(args.ApplicationName, app, args.SettingsYAML); err != nil {
   314  			return errors.Annotate(err, "setting configuration from YAML")
   315  		}
   316  	} else if len(args.SettingsStrings) > 0 {
   317  		if err = ApplicationSetSettingsStrings(app, args.SettingsStrings); err != nil {
   318  			return errors.Trace(err)
   319  		}
   320  	}
   321  	// Update application's constraints.
   322  	if args.Constraints != nil {
   323  		return app.SetConstraints(*args.Constraints)
   324  	}
   325  	return nil
   326  }
   327  
   328  // SetCharm sets the charm for a given for the application.
   329  func (api *API) SetCharm(args params.ApplicationSetCharm) error {
   330  	if err := api.checkCanWrite(); err != nil {
   331  		return err
   332  	}
   333  	// when forced units in error, don't block
   334  	if !args.ForceUnits {
   335  		if err := api.check.ChangeAllowed(); err != nil {
   336  			return errors.Trace(err)
   337  		}
   338  	}
   339  	application, err := api.backend.Application(args.ApplicationName)
   340  	if err != nil {
   341  		return errors.Trace(err)
   342  	}
   343  	channel := csparams.Channel(args.Channel)
   344  	return api.applicationSetCharm(
   345  		args.ApplicationName,
   346  		application,
   347  		args.CharmURL,
   348  		channel,
   349  		args.ConfigSettings,
   350  		args.ConfigSettingsYAML,
   351  		args.ForceSeries,
   352  		args.ForceUnits,
   353  		args.ResourceIDs,
   354  		args.StorageConstraints,
   355  	)
   356  }
   357  
   358  // applicationSetCharm sets the charm for the given for the application.
   359  func (api *API) applicationSetCharm(
   360  	appName string,
   361  	application Application,
   362  	url string,
   363  	channel csparams.Channel,
   364  	configSettingsStrings map[string]string,
   365  	configSettingsYAML string,
   366  	forceSeries,
   367  	forceUnits bool,
   368  	resourceIDs map[string]string,
   369  	storageConstraints map[string]params.StorageConstraints,
   370  ) error {
   371  	curl, err := charm.ParseURL(url)
   372  	if err != nil {
   373  		return errors.Trace(err)
   374  	}
   375  	sch, err := api.backend.Charm(curl)
   376  	if err != nil {
   377  		return errors.Trace(err)
   378  	}
   379  	var settings charm.Settings
   380  	if configSettingsYAML != "" {
   381  		settings, err = sch.Config().ParseSettingsYAML([]byte(configSettingsYAML), appName)
   382  	} else if len(configSettingsStrings) > 0 {
   383  		settings, err = parseSettingsCompatible(sch.Config(), configSettingsStrings)
   384  	}
   385  	if err != nil {
   386  		return errors.Annotate(err, "parsing config settings")
   387  	}
   388  	var stateStorageConstraints map[string]state.StorageConstraints
   389  	if len(storageConstraints) > 0 {
   390  		stateStorageConstraints = make(map[string]state.StorageConstraints)
   391  		for name, cons := range storageConstraints {
   392  			stateCons := state.StorageConstraints{Pool: cons.Pool}
   393  			if cons.Size != nil {
   394  				stateCons.Size = *cons.Size
   395  			}
   396  			if cons.Count != nil {
   397  				stateCons.Count = *cons.Count
   398  			}
   399  			stateStorageConstraints[name] = stateCons
   400  		}
   401  	}
   402  	cfg := state.SetCharmConfig{
   403  		Charm:              api.stateCharm(sch),
   404  		Channel:            channel,
   405  		ConfigSettings:     settings,
   406  		ForceSeries:        forceSeries,
   407  		ForceUnits:         forceUnits,
   408  		ResourceIDs:        resourceIDs,
   409  		StorageConstraints: stateStorageConstraints,
   410  	}
   411  	return application.SetCharm(cfg)
   412  }
   413  
   414  // settingsYamlFromGetYaml will parse a yaml produced by juju get and generate
   415  // charm.Settings from it that can then be sent to the application.
   416  func settingsFromGetYaml(yamlContents map[string]interface{}) (charm.Settings, error) {
   417  	onlySettings := charm.Settings{}
   418  	settingsMap, ok := yamlContents["settings"].(map[interface{}]interface{})
   419  	if !ok {
   420  		return nil, errors.New("unknown format for settings")
   421  	}
   422  
   423  	for setting := range settingsMap {
   424  		s, ok := settingsMap[setting].(map[interface{}]interface{})
   425  		if !ok {
   426  			return nil, errors.Errorf("unknown format for settings section %v", setting)
   427  		}
   428  		// some keys might not have a value, we don't care about those.
   429  		v, ok := s["value"]
   430  		if !ok {
   431  			continue
   432  		}
   433  		stringSetting, ok := setting.(string)
   434  		if !ok {
   435  			return nil, errors.Errorf("unexpected setting key, expected string got %T", setting)
   436  		}
   437  		onlySettings[stringSetting] = v
   438  	}
   439  	return onlySettings, nil
   440  }
   441  
   442  // applicationSetSettingsYAML updates the settings for the given application,
   443  // taking the configuration from a YAML string.
   444  func applicationSetSettingsYAML(appName string, application Application, settings string) error {
   445  	b := []byte(settings)
   446  	var all map[string]interface{}
   447  	if err := goyaml.Unmarshal(b, &all); err != nil {
   448  		return errors.Annotate(err, "parsing settings data")
   449  	}
   450  	// The file is already in the right format.
   451  	if _, ok := all[appName]; !ok {
   452  		changes, err := settingsFromGetYaml(all)
   453  		if err != nil {
   454  			return errors.Annotate(err, "processing YAML generated by get")
   455  		}
   456  		return errors.Annotate(application.UpdateConfigSettings(changes), "updating settings with application YAML")
   457  	}
   458  
   459  	ch, _, err := application.Charm()
   460  	if err != nil {
   461  		return errors.Annotate(err, "obtaining charm for this application")
   462  	}
   463  
   464  	changes, err := ch.Config().ParseSettingsYAML(b, appName)
   465  	if err != nil {
   466  		return errors.Annotate(err, "creating config from YAML")
   467  	}
   468  	return errors.Annotate(application.UpdateConfigSettings(changes), "updating settings")
   469  }
   470  
   471  // GetCharmURL returns the charm URL the given application is
   472  // running at present.
   473  func (api *API) GetCharmURL(args params.ApplicationGet) (params.StringResult, error) {
   474  	if err := api.checkCanWrite(); err != nil {
   475  		return params.StringResult{}, errors.Trace(err)
   476  	}
   477  	application, err := api.backend.Application(args.ApplicationName)
   478  	if err != nil {
   479  		return params.StringResult{}, errors.Trace(err)
   480  	}
   481  	charmURL, _ := application.CharmURL()
   482  	return params.StringResult{Result: charmURL.String()}, nil
   483  }
   484  
   485  // Set implements the server side of Application.Set.
   486  // It does not unset values that are set to an empty string.
   487  // Unset should be used for that.
   488  func (api *API) Set(p params.ApplicationSet) error {
   489  	if err := api.checkCanWrite(); err != nil {
   490  		return err
   491  	}
   492  	if err := api.check.ChangeAllowed(); err != nil {
   493  		return errors.Trace(err)
   494  	}
   495  	app, err := api.backend.Application(p.ApplicationName)
   496  	if err != nil {
   497  		return err
   498  	}
   499  	ch, _, err := app.Charm()
   500  	if err != nil {
   501  		return err
   502  	}
   503  	// Validate the settings.
   504  	changes, err := ch.Config().ParseSettingsStrings(p.Options)
   505  	if err != nil {
   506  		return err
   507  	}
   508  
   509  	return app.UpdateConfigSettings(changes)
   510  
   511  }
   512  
   513  // Unset implements the server side of Client.Unset.
   514  func (api *API) Unset(p params.ApplicationUnset) error {
   515  	if err := api.checkCanWrite(); err != nil {
   516  		return err
   517  	}
   518  	if err := api.check.ChangeAllowed(); err != nil {
   519  		return errors.Trace(err)
   520  	}
   521  	app, err := api.backend.Application(p.ApplicationName)
   522  	if err != nil {
   523  		return err
   524  	}
   525  	settings := make(charm.Settings)
   526  	for _, option := range p.Options {
   527  		settings[option] = nil
   528  	}
   529  	return app.UpdateConfigSettings(settings)
   530  }
   531  
   532  // CharmRelations implements the server side of Application.CharmRelations.
   533  func (api *API) CharmRelations(p params.ApplicationCharmRelations) (params.ApplicationCharmRelationsResults, error) {
   534  	var results params.ApplicationCharmRelationsResults
   535  	if err := api.checkCanRead(); err != nil {
   536  		return results, errors.Trace(err)
   537  	}
   538  
   539  	application, err := api.backend.Application(p.ApplicationName)
   540  	if err != nil {
   541  		return results, errors.Trace(err)
   542  	}
   543  	endpoints, err := application.Endpoints()
   544  	if err != nil {
   545  		return results, errors.Trace(err)
   546  	}
   547  	results.CharmRelations = make([]string, len(endpoints))
   548  	for i, endpoint := range endpoints {
   549  		results.CharmRelations[i] = endpoint.Relation.Name
   550  	}
   551  	return results, nil
   552  }
   553  
   554  // Expose changes the juju-managed firewall to expose any ports that
   555  // were also explicitly marked by units as open.
   556  func (api *API) Expose(args params.ApplicationExpose) error {
   557  	if err := api.checkCanWrite(); err != nil {
   558  		return err
   559  	}
   560  	if err := api.check.ChangeAllowed(); err != nil {
   561  		return errors.Trace(err)
   562  	}
   563  	app, err := api.backend.Application(args.ApplicationName)
   564  	if err != nil {
   565  		return err
   566  	}
   567  	return app.SetExposed()
   568  }
   569  
   570  // Unexpose changes the juju-managed firewall to unexpose any ports that
   571  // were also explicitly marked by units as open.
   572  func (api *API) Unexpose(args params.ApplicationUnexpose) error {
   573  	if err := api.checkCanWrite(); err != nil {
   574  		return err
   575  	}
   576  	if err := api.check.ChangeAllowed(); err != nil {
   577  		return errors.Trace(err)
   578  	}
   579  	app, err := api.backend.Application(args.ApplicationName)
   580  	if err != nil {
   581  		return err
   582  	}
   583  	return app.ClearExposed()
   584  }
   585  
   586  // addApplicationUnits adds a given number of units to an application.
   587  func addApplicationUnits(backend Backend, args params.AddApplicationUnits) ([]*state.Unit, error) {
   588  	application, err := backend.Application(args.ApplicationName)
   589  	if err != nil {
   590  		return nil, errors.Trace(err)
   591  	}
   592  	if args.NumUnits < 1 {
   593  		return nil, errors.New("must add at least one unit")
   594  	}
   595  	return jjj.AddUnits(backend, application, args.ApplicationName, args.NumUnits, args.Placement)
   596  }
   597  
   598  // AddUnits adds a given number of units to an application.
   599  func (api *API) AddUnits(args params.AddApplicationUnits) (params.AddApplicationUnitsResults, error) {
   600  	if err := api.checkCanWrite(); err != nil {
   601  		return params.AddApplicationUnitsResults{}, errors.Trace(err)
   602  	}
   603  	if err := api.check.ChangeAllowed(); err != nil {
   604  		return params.AddApplicationUnitsResults{}, errors.Trace(err)
   605  	}
   606  	units, err := addApplicationUnits(api.backend, args)
   607  	if err != nil {
   608  		return params.AddApplicationUnitsResults{}, errors.Trace(err)
   609  	}
   610  	unitNames := make([]string, len(units))
   611  	for i, unit := range units {
   612  		unitNames[i] = unit.String()
   613  	}
   614  	return params.AddApplicationUnitsResults{Units: unitNames}, nil
   615  }
   616  
   617  // DestroyUnits removes a given set of application units.
   618  func (api *API) DestroyUnits(args params.DestroyApplicationUnits) error {
   619  	if err := api.checkCanWrite(); err != nil {
   620  		return err
   621  	}
   622  	if err := api.check.RemoveAllowed(); err != nil {
   623  		return errors.Trace(err)
   624  	}
   625  	var errs []string
   626  	for _, name := range args.UnitNames {
   627  		unit, err := api.backend.Unit(name)
   628  		switch {
   629  		case errors.IsNotFound(err):
   630  			err = errors.Errorf("unit %q does not exist", name)
   631  		case err != nil:
   632  		case unit.Life() != state.Alive:
   633  			continue
   634  		case unit.IsPrincipal():
   635  			err = unit.Destroy()
   636  		default:
   637  			err = errors.Errorf("unit %q is a subordinate", name)
   638  		}
   639  		if err != nil {
   640  			errs = append(errs, err.Error())
   641  		}
   642  	}
   643  	return common.DestroyErr("units", args.UnitNames, errs)
   644  }
   645  
   646  // Destroy destroys a given application.
   647  func (api *API) Destroy(args params.ApplicationDestroy) error {
   648  	if err := api.checkCanWrite(); err != nil {
   649  		return err
   650  	}
   651  	if err := api.check.RemoveAllowed(); err != nil {
   652  		return errors.Trace(err)
   653  	}
   654  	app, err := api.backend.Application(args.ApplicationName)
   655  	if err != nil {
   656  		return err
   657  	}
   658  	return app.Destroy()
   659  }
   660  
   661  // GetConstraints returns the constraints for a given application.
   662  func (api *API) GetConstraints(args params.GetApplicationConstraints) (params.GetConstraintsResults, error) {
   663  	if err := api.checkCanRead(); err != nil {
   664  		return params.GetConstraintsResults{}, errors.Trace(err)
   665  	}
   666  	app, err := api.backend.Application(args.ApplicationName)
   667  	if err != nil {
   668  		return params.GetConstraintsResults{}, errors.Trace(err)
   669  	}
   670  	cons, err := app.Constraints()
   671  	return params.GetConstraintsResults{cons}, errors.Trace(err)
   672  }
   673  
   674  // SetConstraints sets the constraints for a given application.
   675  func (api *API) SetConstraints(args params.SetConstraints) error {
   676  	if err := api.checkCanWrite(); err != nil {
   677  		return err
   678  	}
   679  	if err := api.check.ChangeAllowed(); err != nil {
   680  		return errors.Trace(err)
   681  	}
   682  	app, err := api.backend.Application(args.ApplicationName)
   683  	if err != nil {
   684  		return err
   685  	}
   686  	return app.SetConstraints(args.Constraints)
   687  }
   688  
   689  // AddRelation adds a relation between the specified endpoints and returns the relation info.
   690  func (api *API) AddRelation(args params.AddRelation) (params.AddRelationResults, error) {
   691  	if err := api.checkCanWrite(); err != nil {
   692  		return params.AddRelationResults{}, errors.Trace(err)
   693  	}
   694  	if err := api.check.ChangeAllowed(); err != nil {
   695  		return params.AddRelationResults{}, errors.Trace(err)
   696  	}
   697  	inEps, err := api.backend.InferEndpoints(args.Endpoints...)
   698  	if err != nil {
   699  		return params.AddRelationResults{}, errors.Trace(err)
   700  	}
   701  	rel, err := api.backend.AddRelation(inEps...)
   702  	if err != nil {
   703  		return params.AddRelationResults{}, errors.Trace(err)
   704  	}
   705  	outEps := make(map[string]params.CharmRelation)
   706  	for _, inEp := range inEps {
   707  		outEp, err := rel.Endpoint(inEp.ApplicationName)
   708  		if err != nil {
   709  			return params.AddRelationResults{}, errors.Trace(err)
   710  		}
   711  		outEps[inEp.ApplicationName] = params.CharmRelation{
   712  			Name:      outEp.Relation.Name,
   713  			Role:      string(outEp.Relation.Role),
   714  			Interface: outEp.Relation.Interface,
   715  			Optional:  outEp.Relation.Optional,
   716  			Limit:     outEp.Relation.Limit,
   717  			Scope:     string(outEp.Relation.Scope),
   718  		}
   719  	}
   720  	return params.AddRelationResults{Endpoints: outEps}, nil
   721  }
   722  
   723  // DestroyRelation removes the relation between the specified endpoints.
   724  func (api *API) DestroyRelation(args params.DestroyRelation) error {
   725  	if err := api.checkCanWrite(); err != nil {
   726  		return err
   727  	}
   728  	if err := api.check.RemoveAllowed(); err != nil {
   729  		return errors.Trace(err)
   730  	}
   731  	eps, err := api.backend.InferEndpoints(args.Endpoints...)
   732  	if err != nil {
   733  		return err
   734  	}
   735  	rel, err := api.backend.EndpointsRelation(eps...)
   736  	if err != nil {
   737  		return err
   738  	}
   739  	return rel.Destroy()
   740  }