github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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  	"github.com/juju/names"
    13  	"gopkg.in/juju/charm.v6-unstable"
    14  
    15  	"github.com/juju/juju/apiserver/common"
    16  	"github.com/juju/juju/apiserver/params"
    17  	jjj "github.com/juju/juju/juju"
    18  	"github.com/juju/juju/state"
    19  	statestorage "github.com/juju/juju/state/storage"
    20  )
    21  
    22  var (
    23  	logger = loggo.GetLogger("juju.apiserver.service")
    24  
    25  	newStateStorage = statestorage.NewStorage
    26  )
    27  
    28  func init() {
    29  	common.RegisterStandardFacade("Service", 1, NewAPI)
    30  }
    31  
    32  // Service defines the methods on the service API end point.
    33  type Service interface {
    34  	SetMetricCredentials(args params.ServiceMetricCredentials) (params.ErrorResults, error)
    35  }
    36  
    37  // API implements the service interface and is the concrete
    38  // implementation of the api end point.
    39  type API struct {
    40  	check      *common.BlockChecker
    41  	state      *state.State
    42  	authorizer common.Authorizer
    43  }
    44  
    45  // NewAPI returns a new service API facade.
    46  func NewAPI(
    47  	st *state.State,
    48  	resources *common.Resources,
    49  	authorizer common.Authorizer,
    50  ) (*API, error) {
    51  	if !authorizer.AuthClient() {
    52  		return nil, common.ErrPerm
    53  	}
    54  
    55  	return &API{
    56  		state:      st,
    57  		authorizer: authorizer,
    58  		check:      common.NewBlockChecker(st),
    59  	}, nil
    60  }
    61  
    62  // SetMetricCredentials sets credentials on the service.
    63  func (api *API) SetMetricCredentials(args params.ServiceMetricCredentials) (params.ErrorResults, error) {
    64  	result := params.ErrorResults{
    65  		Results: make([]params.ErrorResult, len(args.Creds)),
    66  	}
    67  	if len(args.Creds) == 0 {
    68  		return result, nil
    69  	}
    70  	for i, a := range args.Creds {
    71  		service, err := api.state.Service(a.ServiceName)
    72  		if err != nil {
    73  			result.Results[i].Error = common.ServerError(err)
    74  			continue
    75  		}
    76  		err = service.SetMetricCredentials(a.MetricCredentials)
    77  		if err != nil {
    78  			result.Results[i].Error = common.ServerError(err)
    79  		}
    80  	}
    81  	return result, nil
    82  }
    83  
    84  // ServicesDeploy fetches the charms from the charm store and deploys them.
    85  func (api *API) ServicesDeploy(args params.ServicesDeploy) (params.ErrorResults, error) {
    86  	return api.ServicesDeployWithPlacement(args)
    87  }
    88  
    89  // ServicesDeployWithPlacement fetches the charms from the charm store and deploys them
    90  // using the specified placement directives.
    91  func (api *API) ServicesDeployWithPlacement(args params.ServicesDeploy) (params.ErrorResults, error) {
    92  	result := params.ErrorResults{
    93  		Results: make([]params.ErrorResult, len(args.Services)),
    94  	}
    95  	if err := api.check.ChangeAllowed(); err != nil {
    96  		return result, errors.Trace(err)
    97  	}
    98  	owner := api.authorizer.GetAuthTag().String()
    99  	for i, arg := range args.Services {
   100  		err := DeployService(api.state, owner, arg)
   101  		result.Results[i].Error = common.ServerError(err)
   102  	}
   103  	return result, nil
   104  }
   105  
   106  // DeployService fetches the charm from the charm store and deploys it.
   107  // The logic has been factored out into a common function which is called by
   108  // both the legacy API on the client facade, as well as the new service facade.
   109  func DeployService(st *state.State, owner string, args params.ServiceDeploy) error {
   110  	curl, err := charm.ParseURL(args.CharmUrl)
   111  	if err != nil {
   112  		return errors.Trace(err)
   113  	}
   114  	if curl.Revision < 0 {
   115  		return errors.Errorf("charm url must include revision")
   116  	}
   117  
   118  	// Do a quick but not complete validation check before going any further.
   119  	if len(args.Placement) == 0 && args.ToMachineSpec != "" && names.IsValidMachine(args.ToMachineSpec) {
   120  		_, err = st.Machine(args.ToMachineSpec)
   121  		if err != nil {
   122  			return errors.Annotatef(err, `cannot deploy "%v" to machine %v`, args.ServiceName, args.ToMachineSpec)
   123  		}
   124  	}
   125  
   126  	// Try to find the charm URL in state first.
   127  	ch, err := st.Charm(curl)
   128  	if errors.IsNotFound(err) {
   129  		// Clients written to expect 1.16 compatibility require this next block.
   130  		if curl.Schema != "cs" {
   131  			return errors.Errorf(`charm url has unsupported schema %q`, curl.Schema)
   132  		}
   133  		if err = AddCharmWithAuthorization(st, params.AddCharmWithAuthorization{
   134  			URL: args.CharmUrl,
   135  		}); err == nil {
   136  			ch, err = st.Charm(curl)
   137  		}
   138  	}
   139  	if err != nil {
   140  		return errors.Trace(err)
   141  	}
   142  
   143  	var settings charm.Settings
   144  	if len(args.ConfigYAML) > 0 {
   145  		settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName)
   146  	} else if len(args.Config) > 0 {
   147  		// Parse config in a compatible way (see function comment).
   148  		settings, err = parseSettingsCompatible(ch, args.Config)
   149  	}
   150  	if err != nil {
   151  		return errors.Trace(err)
   152  	}
   153  	// Convert network tags to names for any given networks.
   154  	requestedNetworks, err := networkTagsToNames(args.Networks)
   155  	if err != nil {
   156  		return errors.Trace(err)
   157  	}
   158  
   159  	_, err = jjj.DeployService(st,
   160  		jjj.DeployServiceParams{
   161  			ServiceName: args.ServiceName,
   162  			// TODO(dfc) ServiceOwner should be a tag
   163  			ServiceOwner:   owner,
   164  			Charm:          ch,
   165  			NumUnits:       args.NumUnits,
   166  			ConfigSettings: settings,
   167  			Constraints:    args.Constraints,
   168  			ToMachineSpec:  args.ToMachineSpec,
   169  			Placement:      args.Placement,
   170  			Networks:       requestedNetworks,
   171  			Storage:        args.Storage,
   172  		})
   173  	return err
   174  }
   175  
   176  // ServiceSetSettingsStrings updates the settings for the given service,
   177  // taking the configuration from a map of strings.
   178  func ServiceSetSettingsStrings(service *state.Service, settings map[string]string) error {
   179  	ch, _, err := service.Charm()
   180  	if err != nil {
   181  		return err
   182  	}
   183  	// Parse config in a compatible way (see function comment).
   184  	changes, err := parseSettingsCompatible(ch, settings)
   185  	if err != nil {
   186  		return err
   187  	}
   188  	return service.UpdateConfigSettings(changes)
   189  }
   190  
   191  func networkTagsToNames(tags []string) ([]string, error) {
   192  	netNames := make([]string, len(tags))
   193  	for i, tag := range tags {
   194  		t, err := names.ParseNetworkTag(tag)
   195  		if err != nil {
   196  			return nil, err
   197  		}
   198  		netNames[i] = t.Id()
   199  	}
   200  	return netNames, nil
   201  }
   202  
   203  // parseSettingsCompatible parses setting strings in a way that is
   204  // compatible with the behavior before this CL based on the issue
   205  // http://pad.lv/1194945. Until then setting an option to an empty
   206  // string caused it to reset to the default value. We now allow
   207  // empty strings as actual values, but we want to preserve the API
   208  // behavior.
   209  func parseSettingsCompatible(ch *state.Charm, settings map[string]string) (charm.Settings, error) {
   210  	setSettings := map[string]string{}
   211  	unsetSettings := charm.Settings{}
   212  	// Split settings into those which set and those which unset a value.
   213  	for name, value := range settings {
   214  		if value == "" {
   215  			unsetSettings[name] = nil
   216  			continue
   217  		}
   218  		setSettings[name] = value
   219  	}
   220  	// Validate the settings.
   221  	changes, err := ch.Config().ParseSettingsStrings(setSettings)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	// Validate the unsettings and merge them into the changes.
   226  	unsetSettings, err = ch.Config().ValidateSettings(unsetSettings)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  	for name := range unsetSettings {
   231  		changes[name] = nil
   232  	}
   233  	return changes, nil
   234  }