github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/service/service.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package service contains api calls for functionality
     5  // related to deploying and managing services and their
     6  // related charms.
     7  package service
     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/params"
    18  	"github.com/juju/juju/instance"
    19  	jjj "github.com/juju/juju/juju"
    20  	"github.com/juju/juju/state"
    21  	statestorage "github.com/juju/juju/state/storage"
    22  )
    23  
    24  var (
    25  	logger = loggo.GetLogger("juju.apiserver.service")
    26  
    27  	newStateStorage = statestorage.NewStorage
    28  )
    29  
    30  func init() {
    31  	common.RegisterStandardFacade("Service", 3, NewAPI)
    32  }
    33  
    34  // Service defines the methods on the service API end point.
    35  type Service interface {
    36  	SetMetricCredentials(args params.ServiceMetricCredentials) (params.ErrorResults, error)
    37  }
    38  
    39  // API implements the service interface and is the concrete
    40  // implementation of the api end point.
    41  type API struct {
    42  	check      *common.BlockChecker
    43  	state      *state.State
    44  	authorizer common.Authorizer
    45  }
    46  
    47  // NewAPI returns a new service API facade.
    48  func NewAPI(
    49  	st *state.State,
    50  	resources *common.Resources,
    51  	authorizer common.Authorizer,
    52  ) (*API, error) {
    53  	if !authorizer.AuthClient() {
    54  		return nil, common.ErrPerm
    55  	}
    56  
    57  	return &API{
    58  		state:      st,
    59  		authorizer: authorizer,
    60  		check:      common.NewBlockChecker(st),
    61  	}, nil
    62  }
    63  
    64  // SetMetricCredentials sets credentials on the service.
    65  func (api *API) SetMetricCredentials(args params.ServiceMetricCredentials) (params.ErrorResults, error) {
    66  	result := params.ErrorResults{
    67  		Results: make([]params.ErrorResult, len(args.Creds)),
    68  	}
    69  	if len(args.Creds) == 0 {
    70  		return result, nil
    71  	}
    72  	for i, a := range args.Creds {
    73  		service, err := api.state.Service(a.ServiceName)
    74  		if err != nil {
    75  			result.Results[i].Error = common.ServerError(err)
    76  			continue
    77  		}
    78  		err = service.SetMetricCredentials(a.MetricCredentials)
    79  		if err != nil {
    80  			result.Results[i].Error = common.ServerError(err)
    81  		}
    82  	}
    83  	return result, nil
    84  }
    85  
    86  // Deploy fetches the charms from the charm store and deploys them
    87  // using the specified placement directives.
    88  func (api *API) Deploy(args params.ServicesDeploy) (params.ErrorResults, error) {
    89  	result := params.ErrorResults{
    90  		Results: make([]params.ErrorResult, len(args.Services)),
    91  	}
    92  	if err := api.check.ChangeAllowed(); err != nil {
    93  		return result, errors.Trace(err)
    94  	}
    95  	owner := api.authorizer.GetAuthTag().String()
    96  	for i, arg := range args.Services {
    97  		err := deployService(api.state, owner, arg)
    98  		result.Results[i].Error = common.ServerError(err)
    99  	}
   100  	return result, nil
   101  }
   102  
   103  // DeployService fetches the charm from the charm store and deploys it.
   104  // The logic has been factored out into a common function which is called by
   105  // both the legacy API on the client facade, as well as the new service facade.
   106  func deployService(st *state.State, owner string, args params.ServiceDeploy) error {
   107  	curl, err := charm.ParseURL(args.CharmUrl)
   108  	if err != nil {
   109  		return errors.Trace(err)
   110  	}
   111  	if curl.Revision < 0 {
   112  		return errors.Errorf("charm url must include revision")
   113  	}
   114  
   115  	// Do a quick but not complete validation check before going any further.
   116  	for _, p := range args.Placement {
   117  		if p.Scope != instance.MachineScope {
   118  			continue
   119  		}
   120  		_, err = st.Machine(p.Directive)
   121  		if err != nil {
   122  			return errors.Annotatef(err, `cannot deploy "%v" to machine %v`, args.ServiceName, p.Directive)
   123  		}
   124  	}
   125  
   126  	// Try to find the charm URL in state first.
   127  	ch, err := st.Charm(curl)
   128  	// TODO(wallyworld) - remove for 2.0 beta4
   129  	if errors.IsNotFound(err) {
   130  		// Clients written to expect 1.16 compatibility require this next block.
   131  		if curl.Schema != "cs" {
   132  			return errors.Errorf(`charm url has unsupported schema %q`, curl.Schema)
   133  		}
   134  		if err = AddCharmWithAuthorization(st, params.AddCharmWithAuthorization{
   135  			URL: args.CharmUrl,
   136  		}); err == nil {
   137  			ch, err = st.Charm(curl)
   138  		}
   139  	}
   140  	if err != nil {
   141  		return errors.Trace(err)
   142  	}
   143  
   144  	if err := checkMinVersion(ch); err != nil {
   145  		return errors.Trace(err)
   146  	}
   147  
   148  	var settings charm.Settings
   149  	if len(args.ConfigYAML) > 0 {
   150  		settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName)
   151  	} else if len(args.Config) > 0 {
   152  		// Parse config in a compatible way (see function comment).
   153  		settings, err = parseSettingsCompatible(ch, args.Config)
   154  	}
   155  	if err != nil {
   156  		return errors.Trace(err)
   157  	}
   158  
   159  	channel := csparams.Channel(args.Channel)
   160  
   161  	_, err = jjj.DeployService(st,
   162  		jjj.DeployServiceParams{
   163  			ServiceName: args.ServiceName,
   164  			Series:      args.Series,
   165  			// TODO(dfc) ServiceOwner should be a tag
   166  			ServiceOwner:     owner,
   167  			Charm:            ch,
   168  			Channel:          channel,
   169  			NumUnits:         args.NumUnits,
   170  			ConfigSettings:   settings,
   171  			Constraints:      args.Constraints,
   172  			Placement:        args.Placement,
   173  			Storage:          args.Storage,
   174  			EndpointBindings: args.EndpointBindings,
   175  			Resources:        args.Resources,
   176  		})
   177  	return errors.Trace(err)
   178  }
   179  
   180  // ServiceSetSettingsStrings updates the settings for the given service,
   181  // taking the configuration from a map of strings.
   182  func ServiceSetSettingsStrings(service *state.Service, settings map[string]string) error {
   183  	ch, _, err := service.Charm()
   184  	if err != nil {
   185  		return errors.Trace(err)
   186  	}
   187  	// Parse config in a compatible way (see function comment).
   188  	changes, err := parseSettingsCompatible(ch, settings)
   189  	if err != nil {
   190  		return errors.Trace(err)
   191  	}
   192  	return service.UpdateConfigSettings(changes)
   193  }
   194  
   195  // parseSettingsCompatible parses setting strings in a way that is
   196  // compatible with the behavior before this CL based on the issue
   197  // http://pad.lv/1194945. Until then setting an option to an empty
   198  // string caused it to reset to the default value. We now allow
   199  // empty strings as actual values, but we want to preserve the API
   200  // behavior.
   201  func parseSettingsCompatible(ch *state.Charm, settings map[string]string) (charm.Settings, error) {
   202  	setSettings := map[string]string{}
   203  	unsetSettings := charm.Settings{}
   204  	// Split settings into those which set and those which unset a value.
   205  	for name, value := range settings {
   206  		if value == "" {
   207  			unsetSettings[name] = nil
   208  			continue
   209  		}
   210  		setSettings[name] = value
   211  	}
   212  	// Validate the settings.
   213  	changes, err := ch.Config().ParseSettingsStrings(setSettings)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	// Validate the unsettings and merge them into the changes.
   218  	unsetSettings, err = ch.Config().ValidateSettings(unsetSettings)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  	for name := range unsetSettings {
   223  		changes[name] = nil
   224  	}
   225  	return changes, nil
   226  }
   227  
   228  // Update updates the service attributes, including charm URL,
   229  // minimum number of units, settings and constraints.
   230  // All parameters in params.ServiceUpdate except the service name are optional.
   231  func (api *API) Update(args params.ServiceUpdate) error {
   232  	if !args.ForceCharmUrl {
   233  		if err := api.check.ChangeAllowed(); err != nil {
   234  			return errors.Trace(err)
   235  		}
   236  	}
   237  	svc, err := api.state.Service(args.ServiceName)
   238  	if err != nil {
   239  		return errors.Trace(err)
   240  	}
   241  	// Set the charm for the given service.
   242  	if args.CharmUrl != "" {
   243  		// For now we do not support changing the channel through Update().
   244  		// TODO(ericsnow) Support it?
   245  		channel := svc.Channel()
   246  		if err = api.serviceSetCharm(svc, args.CharmUrl, channel, args.ForceSeries, args.ForceCharmUrl, nil); err != nil {
   247  			return errors.Trace(err)
   248  		}
   249  	}
   250  	// Set the minimum number of units for the given service.
   251  	if args.MinUnits != nil {
   252  		if err = svc.SetMinUnits(*args.MinUnits); err != nil {
   253  			return errors.Trace(err)
   254  		}
   255  	}
   256  	// Set up service's settings.
   257  	if args.SettingsYAML != "" {
   258  		if err = serviceSetSettingsYAML(svc, args.SettingsYAML); err != nil {
   259  			return errors.Annotate(err, "setting configuration from YAML")
   260  		}
   261  	} else if len(args.SettingsStrings) > 0 {
   262  		if err = ServiceSetSettingsStrings(svc, args.SettingsStrings); err != nil {
   263  			return errors.Trace(err)
   264  		}
   265  	}
   266  	// Update service's constraints.
   267  	if args.Constraints != nil {
   268  		return svc.SetConstraints(*args.Constraints)
   269  	}
   270  	return nil
   271  }
   272  
   273  // SetCharm sets the charm for a given service.
   274  func (api *API) SetCharm(args params.ServiceSetCharm) error {
   275  	// when forced units in error, don't block
   276  	if !args.ForceUnits {
   277  		if err := api.check.ChangeAllowed(); err != nil {
   278  			return errors.Trace(err)
   279  		}
   280  	}
   281  	service, err := api.state.Service(args.ServiceName)
   282  	if err != nil {
   283  		return errors.Trace(err)
   284  	}
   285  	channel := csparams.Channel(args.Channel)
   286  	return api.serviceSetCharm(service, args.CharmUrl, channel, args.ForceSeries, args.ForceUnits, args.ResourceIDs)
   287  }
   288  
   289  // serviceSetCharm sets the charm for the given service.
   290  func (api *API) serviceSetCharm(service *state.Service, url string, channel csparams.Channel, forceSeries, forceUnits bool, resourceIDs map[string]string) error {
   291  	curl, err := charm.ParseURL(url)
   292  	if err != nil {
   293  		return errors.Trace(err)
   294  	}
   295  	sch, err := api.state.Charm(curl)
   296  	if err != nil {
   297  		return errors.Trace(err)
   298  	}
   299  	cfg := state.SetCharmConfig{
   300  		Charm:       sch,
   301  		Channel:     channel,
   302  		ForceSeries: forceSeries,
   303  		ForceUnits:  forceUnits,
   304  		ResourceIDs: resourceIDs,
   305  	}
   306  	return service.SetCharm(cfg)
   307  }
   308  
   309  // settingsYamlFromGetYaml will parse a yaml produced by juju get and generate
   310  // charm.Settings from it that can then be sent to the service.
   311  func settingsFromGetYaml(yamlContents map[string]interface{}) (charm.Settings, error) {
   312  	onlySettings := charm.Settings{}
   313  	settingsMap, ok := yamlContents["settings"].(map[interface{}]interface{})
   314  	if !ok {
   315  		return nil, errors.New("unknown format for settings")
   316  	}
   317  
   318  	for setting := range settingsMap {
   319  		s, ok := settingsMap[setting].(map[interface{}]interface{})
   320  		if !ok {
   321  			return nil, errors.Errorf("unknown format for settings section %v", setting)
   322  		}
   323  		// some keys might not have a value, we don't care about those.
   324  		v, ok := s["value"]
   325  		if !ok {
   326  			continue
   327  		}
   328  		stringSetting, ok := setting.(string)
   329  		if !ok {
   330  			return nil, errors.Errorf("unexpected setting key, expected string got %T", setting)
   331  		}
   332  		onlySettings[stringSetting] = v
   333  	}
   334  	return onlySettings, nil
   335  }
   336  
   337  // serviceSetSettingsYAML updates the settings for the given service,
   338  // taking the configuration from a YAML string.
   339  func serviceSetSettingsYAML(service *state.Service, settings string) error {
   340  	b := []byte(settings)
   341  	var all map[string]interface{}
   342  	if err := goyaml.Unmarshal(b, &all); err != nil {
   343  		return errors.Annotate(err, "parsing settings data")
   344  	}
   345  	// The file is already in the right format.
   346  	if _, ok := all[service.Name()]; !ok {
   347  		changes, err := settingsFromGetYaml(all)
   348  		if err != nil {
   349  			return errors.Annotate(err, "processing YAML generated by get")
   350  		}
   351  		return errors.Annotate(service.UpdateConfigSettings(changes), "updating settings with service YAML")
   352  	}
   353  
   354  	ch, _, err := service.Charm()
   355  	if err != nil {
   356  		return errors.Annotate(err, "obtaining charm for this service")
   357  	}
   358  
   359  	changes, err := ch.Config().ParseSettingsYAML(b, service.Name())
   360  	if err != nil {
   361  		return errors.Annotate(err, "creating config from YAML")
   362  	}
   363  	return errors.Annotate(service.UpdateConfigSettings(changes), "updating settings")
   364  }
   365  
   366  // GetCharmURL returns the charm URL the given service is
   367  // running at present.
   368  func (api *API) GetCharmURL(args params.ServiceGet) (params.StringResult, error) {
   369  	service, err := api.state.Service(args.ServiceName)
   370  	if err != nil {
   371  		return params.StringResult{}, err
   372  	}
   373  	charmURL, _ := service.CharmURL()
   374  	return params.StringResult{Result: charmURL.String()}, nil
   375  }
   376  
   377  // Set implements the server side of Service.Set.
   378  // It does not unset values that are set to an empty string.
   379  // Unset should be used for that.
   380  func (api *API) Set(p params.ServiceSet) error {
   381  	if err := api.check.ChangeAllowed(); err != nil {
   382  		return errors.Trace(err)
   383  	}
   384  	svc, err := api.state.Service(p.ServiceName)
   385  	if err != nil {
   386  		return err
   387  	}
   388  	ch, _, err := svc.Charm()
   389  	if err != nil {
   390  		return err
   391  	}
   392  	// Validate the settings.
   393  	changes, err := ch.Config().ParseSettingsStrings(p.Options)
   394  	if err != nil {
   395  		return err
   396  	}
   397  
   398  	return svc.UpdateConfigSettings(changes)
   399  
   400  }
   401  
   402  // Unset implements the server side of Client.Unset.
   403  func (api *API) Unset(p params.ServiceUnset) error {
   404  	if err := api.check.ChangeAllowed(); err != nil {
   405  		return errors.Trace(err)
   406  	}
   407  	svc, err := api.state.Service(p.ServiceName)
   408  	if err != nil {
   409  		return err
   410  	}
   411  	settings := make(charm.Settings)
   412  	for _, option := range p.Options {
   413  		settings[option] = nil
   414  	}
   415  	return svc.UpdateConfigSettings(settings)
   416  }
   417  
   418  // CharmRelations implements the server side of Service.CharmRelations.
   419  func (api *API) CharmRelations(p params.ServiceCharmRelations) (params.ServiceCharmRelationsResults, error) {
   420  	var results params.ServiceCharmRelationsResults
   421  	service, err := api.state.Service(p.ServiceName)
   422  	if err != nil {
   423  		return results, err
   424  	}
   425  	endpoints, err := service.Endpoints()
   426  	if err != nil {
   427  		return results, err
   428  	}
   429  	results.CharmRelations = make([]string, len(endpoints))
   430  	for i, endpoint := range endpoints {
   431  		results.CharmRelations[i] = endpoint.Relation.Name
   432  	}
   433  	return results, nil
   434  }
   435  
   436  // Expose changes the juju-managed firewall to expose any ports that
   437  // were also explicitly marked by units as open.
   438  func (api *API) Expose(args params.ServiceExpose) error {
   439  	if err := api.check.ChangeAllowed(); err != nil {
   440  		return errors.Trace(err)
   441  	}
   442  	svc, err := api.state.Service(args.ServiceName)
   443  	if err != nil {
   444  		return err
   445  	}
   446  	return svc.SetExposed()
   447  }
   448  
   449  // Unexpose changes the juju-managed firewall to unexpose any ports that
   450  // were also explicitly marked by units as open.
   451  func (api *API) Unexpose(args params.ServiceUnexpose) error {
   452  	if err := api.check.ChangeAllowed(); err != nil {
   453  		return errors.Trace(err)
   454  	}
   455  	svc, err := api.state.Service(args.ServiceName)
   456  	if err != nil {
   457  		return err
   458  	}
   459  	return svc.ClearExposed()
   460  }
   461  
   462  // addServiceUnits adds a given number of units to a service.
   463  func addServiceUnits(st *state.State, args params.AddServiceUnits) ([]*state.Unit, error) {
   464  	service, err := st.Service(args.ServiceName)
   465  	if err != nil {
   466  		return nil, err
   467  	}
   468  	if args.NumUnits < 1 {
   469  		return nil, errors.New("must add at least one unit")
   470  	}
   471  	return jjj.AddUnits(st, service, args.NumUnits, args.Placement)
   472  }
   473  
   474  // AddUnits adds a given number of units to a service.
   475  func (api *API) AddUnits(args params.AddServiceUnits) (params.AddServiceUnitsResults, error) {
   476  	if err := api.check.ChangeAllowed(); err != nil {
   477  		return params.AddServiceUnitsResults{}, errors.Trace(err)
   478  	}
   479  	units, err := addServiceUnits(api.state, args)
   480  	if err != nil {
   481  		return params.AddServiceUnitsResults{}, err
   482  	}
   483  	unitNames := make([]string, len(units))
   484  	for i, unit := range units {
   485  		unitNames[i] = unit.String()
   486  	}
   487  	return params.AddServiceUnitsResults{Units: unitNames}, nil
   488  }
   489  
   490  // DestroyUnits removes a given set of service units.
   491  func (api *API) DestroyUnits(args params.DestroyServiceUnits) error {
   492  	if err := api.check.RemoveAllowed(); err != nil {
   493  		return errors.Trace(err)
   494  	}
   495  	var errs []string
   496  	for _, name := range args.UnitNames {
   497  		unit, err := api.state.Unit(name)
   498  		switch {
   499  		case errors.IsNotFound(err):
   500  			err = errors.Errorf("unit %q does not exist", name)
   501  		case err != nil:
   502  		case unit.Life() != state.Alive:
   503  			continue
   504  		case unit.IsPrincipal():
   505  			err = unit.Destroy()
   506  		default:
   507  			err = errors.Errorf("unit %q is a subordinate", name)
   508  		}
   509  		if err != nil {
   510  			errs = append(errs, err.Error())
   511  		}
   512  	}
   513  	return common.DestroyErr("units", args.UnitNames, errs)
   514  }
   515  
   516  // Destroy destroys a given service.
   517  func (api *API) Destroy(args params.ServiceDestroy) error {
   518  	if err := api.check.RemoveAllowed(); err != nil {
   519  		return errors.Trace(err)
   520  	}
   521  	svc, err := api.state.Service(args.ServiceName)
   522  	if err != nil {
   523  		return err
   524  	}
   525  	return svc.Destroy()
   526  }
   527  
   528  // GetConstraints returns the constraints for a given service.
   529  func (api *API) GetConstraints(args params.GetServiceConstraints) (params.GetConstraintsResults, error) {
   530  	svc, err := api.state.Service(args.ServiceName)
   531  	if err != nil {
   532  		return params.GetConstraintsResults{}, err
   533  	}
   534  	cons, err := svc.Constraints()
   535  	return params.GetConstraintsResults{cons}, err
   536  }
   537  
   538  // SetConstraints sets the constraints for a given service.
   539  func (api *API) SetConstraints(args params.SetConstraints) error {
   540  	if err := api.check.ChangeAllowed(); err != nil {
   541  		return errors.Trace(err)
   542  	}
   543  	svc, err := api.state.Service(args.ServiceName)
   544  	if err != nil {
   545  		return err
   546  	}
   547  	return svc.SetConstraints(args.Constraints)
   548  }
   549  
   550  // AddRelation adds a relation between the specified endpoints and returns the relation info.
   551  func (api *API) AddRelation(args params.AddRelation) (params.AddRelationResults, error) {
   552  	if err := api.check.ChangeAllowed(); err != nil {
   553  		return params.AddRelationResults{}, errors.Trace(err)
   554  	}
   555  	inEps, err := api.state.InferEndpoints(args.Endpoints...)
   556  	if err != nil {
   557  		return params.AddRelationResults{}, err
   558  	}
   559  	rel, err := api.state.AddRelation(inEps...)
   560  	if err != nil {
   561  		return params.AddRelationResults{}, err
   562  	}
   563  	outEps := make(map[string]charm.Relation)
   564  	for _, inEp := range inEps {
   565  		outEp, err := rel.Endpoint(inEp.ServiceName)
   566  		if err != nil {
   567  			return params.AddRelationResults{}, err
   568  		}
   569  		outEps[inEp.ServiceName] = outEp.Relation
   570  	}
   571  	return params.AddRelationResults{Endpoints: outEps}, nil
   572  }
   573  
   574  // DestroyRelation removes the relation between the specified endpoints.
   575  func (api *API) DestroyRelation(args params.DestroyRelation) error {
   576  	if err := api.check.RemoveAllowed(); err != nil {
   577  		return errors.Trace(err)
   578  	}
   579  	eps, err := api.state.InferEndpoints(args.Endpoints...)
   580  	if err != nil {
   581  		return err
   582  	}
   583  	rel, err := api.state.EndpointsRelation(eps...)
   584  	if err != nil {
   585  		return err
   586  	}
   587  	return rel.Destroy()
   588  }