github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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.v5"
    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  	result := params.ErrorResults{
    87  		Results: make([]params.ErrorResult, len(args.Services)),
    88  	}
    89  	if err := api.check.ChangeAllowed(); err != nil {
    90  		return result, errors.Trace(err)
    91  	}
    92  	owner := api.authorizer.GetAuthTag().String()
    93  	for i, arg := range args.Services {
    94  		err := DeployService(api.state, owner, arg)
    95  		result.Results[i].Error = common.ServerError(err)
    96  	}
    97  	return result, nil
    98  }
    99  
   100  // DeployService fetches the charm from the charm store and deploys it.
   101  // The logic has been factored out into a common function which is called by
   102  // both the legacy API on the client facade, as well as the new service facade.
   103  func DeployService(st *state.State, owner string, args params.ServiceDeploy) error {
   104  	curl, err := charm.ParseURL(args.CharmUrl)
   105  	if err != nil {
   106  		return errors.Trace(err)
   107  	}
   108  	if curl.Revision < 0 {
   109  		return errors.Errorf("charm url must include revision")
   110  	}
   111  
   112  	if args.ToMachineSpec != "" && names.IsValidMachine(args.ToMachineSpec) {
   113  		_, err = st.Machine(args.ToMachineSpec)
   114  		if err != nil {
   115  			return errors.Annotatef(err, `cannot deploy "%v" to machine %v`, args.ServiceName, args.ToMachineSpec)
   116  		}
   117  	}
   118  
   119  	// Try to find the charm URL in state first.
   120  	ch, err := st.Charm(curl)
   121  	if errors.IsNotFound(err) {
   122  		// Clients written to expect 1.16 compatibility require this next block.
   123  		if curl.Schema != "cs" {
   124  			return errors.Errorf(`charm url has unsupported schema %q`, curl.Schema)
   125  		}
   126  		if err = AddCharmWithAuthorization(st, params.AddCharmWithAuthorization{
   127  			URL: args.CharmUrl,
   128  		}); err == nil {
   129  			ch, err = st.Charm(curl)
   130  		}
   131  	}
   132  	if err != nil {
   133  		return errors.Trace(err)
   134  	}
   135  
   136  	var settings charm.Settings
   137  	if len(args.ConfigYAML) > 0 {
   138  		settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName)
   139  	} else if len(args.Config) > 0 {
   140  		// Parse config in a compatible way (see function comment).
   141  		settings, err = parseSettingsCompatible(ch, args.Config)
   142  	}
   143  	if err != nil {
   144  		return errors.Trace(err)
   145  	}
   146  	// Convert network tags to names for any given networks.
   147  	requestedNetworks, err := networkTagsToNames(args.Networks)
   148  	if err != nil {
   149  		return errors.Trace(err)
   150  	}
   151  
   152  	_, err = jjj.DeployService(st,
   153  		jjj.DeployServiceParams{
   154  			ServiceName: args.ServiceName,
   155  			// TODO(dfc) ServiceOwner should be a tag
   156  			ServiceOwner:   owner,
   157  			Charm:          ch,
   158  			NumUnits:       args.NumUnits,
   159  			ConfigSettings: settings,
   160  			Constraints:    args.Constraints,
   161  			ToMachineSpec:  args.ToMachineSpec,
   162  			Networks:       requestedNetworks,
   163  			Storage:        args.Storage,
   164  		})
   165  	return err
   166  }
   167  
   168  // ServiceSetSettingsStrings updates the settings for the given service,
   169  // taking the configuration from a map of strings.
   170  func ServiceSetSettingsStrings(service *state.Service, settings map[string]string) error {
   171  	ch, _, err := service.Charm()
   172  	if err != nil {
   173  		return err
   174  	}
   175  	// Parse config in a compatible way (see function comment).
   176  	changes, err := parseSettingsCompatible(ch, settings)
   177  	if err != nil {
   178  		return err
   179  	}
   180  	return service.UpdateConfigSettings(changes)
   181  }
   182  
   183  func networkTagsToNames(tags []string) ([]string, error) {
   184  	netNames := make([]string, len(tags))
   185  	for i, tag := range tags {
   186  		t, err := names.ParseNetworkTag(tag)
   187  		if err != nil {
   188  			return nil, err
   189  		}
   190  		netNames[i] = t.Id()
   191  	}
   192  	return netNames, nil
   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  }