github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/client/client.go (about)

     1  // Copyright 2013, 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package client
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"gopkg.in/juju/names.v2"
    12  
    13  	"github.com/juju/juju/apiserver/application"
    14  	"github.com/juju/juju/apiserver/common"
    15  	"github.com/juju/juju/apiserver/facade"
    16  	"github.com/juju/juju/apiserver/modelconfig"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/environs"
    19  	"github.com/juju/juju/environs/config"
    20  	"github.com/juju/juju/environs/manual"
    21  	"github.com/juju/juju/instance"
    22  	"github.com/juju/juju/network"
    23  	"github.com/juju/juju/permission"
    24  	"github.com/juju/juju/state"
    25  	"github.com/juju/juju/state/stateenvirons"
    26  	jujuversion "github.com/juju/juju/version"
    27  )
    28  
    29  func init() {
    30  	common.RegisterStandardFacade("Client", 1, newClient)
    31  }
    32  
    33  var logger = loggo.GetLogger("juju.apiserver.client")
    34  
    35  type API struct {
    36  	stateAccessor Backend
    37  	auth          facade.Authorizer
    38  	resources     facade.Resources
    39  	client        *Client
    40  	// statusSetter provides common methods for updating an entity's provisioning status.
    41  	statusSetter *common.StatusSetter
    42  	toolsFinder  *common.ToolsFinder
    43  }
    44  
    45  // TODO(wallyworld) - remove this method
    46  // state returns a state.State instance for this API.
    47  // Until all code is refactored to use interfaces, we
    48  // need this helper to keep older code happy.
    49  func (api *API) state() *state.State {
    50  	return api.stateAccessor.(stateShim).State
    51  }
    52  
    53  // Client serves client-specific API methods.
    54  type Client struct {
    55  	// TODO(wallyworld) - we'll retain model config facade methods
    56  	// on the client facade until GUI and Python client library are updated.
    57  	*modelconfig.ModelConfigAPI
    58  
    59  	api        *API
    60  	newEnviron func() (environs.Environ, error)
    61  	check      *common.BlockChecker
    62  }
    63  
    64  func (c *Client) checkCanRead() error {
    65  	canRead, err := c.api.auth.HasPermission(permission.ReadAccess, c.api.stateAccessor.ModelTag())
    66  	if err != nil {
    67  		return errors.Trace(err)
    68  	}
    69  	if !canRead {
    70  		return common.ErrPerm
    71  	}
    72  	return nil
    73  }
    74  
    75  func (c *Client) checkCanWrite() error {
    76  	canWrite, err := c.api.auth.HasPermission(permission.WriteAccess, c.api.stateAccessor.ModelTag())
    77  	if err != nil {
    78  		return errors.Trace(err)
    79  	}
    80  	if !canWrite {
    81  		return common.ErrPerm
    82  	}
    83  	return nil
    84  }
    85  
    86  func newClient(st *state.State, resources facade.Resources, authorizer facade.Authorizer) (*Client, error) {
    87  	urlGetter := common.NewToolsURLGetter(st.ModelUUID(), st)
    88  	configGetter := stateenvirons.EnvironConfigGetter{st}
    89  	statusSetter := common.NewStatusSetter(st, common.AuthAlways())
    90  	toolsFinder := common.NewToolsFinder(configGetter, st, urlGetter)
    91  	newEnviron := func() (environs.Environ, error) {
    92  		return environs.GetEnviron(configGetter, environs.New)
    93  	}
    94  	blockChecker := common.NewBlockChecker(st)
    95  	modelConfigAPI, err := modelconfig.NewModelConfigAPI(st, authorizer)
    96  	if err != nil {
    97  		return nil, errors.Trace(err)
    98  	}
    99  	return NewClient(
   100  		NewStateBackend(st),
   101  		modelConfigAPI,
   102  		resources,
   103  		authorizer,
   104  		statusSetter,
   105  		toolsFinder,
   106  		newEnviron,
   107  		blockChecker,
   108  	)
   109  }
   110  
   111  // NewClient creates a new instance of the Client Facade.
   112  func NewClient(
   113  	st Backend,
   114  	modelConfigAPI *modelconfig.ModelConfigAPI,
   115  	resources facade.Resources,
   116  	authorizer facade.Authorizer,
   117  	statusSetter *common.StatusSetter,
   118  	toolsFinder *common.ToolsFinder,
   119  	newEnviron func() (environs.Environ, error),
   120  	blockChecker *common.BlockChecker,
   121  ) (*Client, error) {
   122  	if !authorizer.AuthClient() {
   123  		return nil, common.ErrPerm
   124  	}
   125  	client := &Client{
   126  		modelConfigAPI,
   127  		&API{
   128  			stateAccessor: st,
   129  			auth:          authorizer,
   130  			resources:     resources,
   131  			statusSetter:  statusSetter,
   132  			toolsFinder:   toolsFinder,
   133  		},
   134  		newEnviron,
   135  		blockChecker,
   136  	}
   137  	return client, nil
   138  }
   139  
   140  func (c *Client) WatchAll() (params.AllWatcherId, error) {
   141  	if err := c.checkCanRead(); err != nil {
   142  		return params.AllWatcherId{}, err
   143  	}
   144  	w := c.api.stateAccessor.Watch()
   145  	return params.AllWatcherId{
   146  		AllWatcherId: c.api.resources.Register(w),
   147  	}, nil
   148  }
   149  
   150  // Resolved implements the server side of Client.Resolved.
   151  func (c *Client) Resolved(p params.Resolved) error {
   152  	if err := c.checkCanWrite(); err != nil {
   153  		return err
   154  	}
   155  	if err := c.check.ChangeAllowed(); err != nil {
   156  		return errors.Trace(err)
   157  	}
   158  	unit, err := c.api.stateAccessor.Unit(p.UnitName)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	return unit.Resolve(p.Retry)
   163  }
   164  
   165  // PublicAddress implements the server side of Client.PublicAddress.
   166  func (c *Client) PublicAddress(p params.PublicAddress) (results params.PublicAddressResults, err error) {
   167  	if err := c.checkCanRead(); err != nil {
   168  		return params.PublicAddressResults{}, err
   169  	}
   170  
   171  	switch {
   172  	case names.IsValidMachine(p.Target):
   173  		machine, err := c.api.stateAccessor.Machine(p.Target)
   174  		if err != nil {
   175  			return results, err
   176  		}
   177  		addr, err := machine.PublicAddress()
   178  		if err != nil {
   179  			return results, errors.Annotatef(err, "error fetching address for machine %q", machine)
   180  		}
   181  		return params.PublicAddressResults{PublicAddress: addr.Value}, nil
   182  
   183  	case names.IsValidUnit(p.Target):
   184  		unit, err := c.api.stateAccessor.Unit(p.Target)
   185  		if err != nil {
   186  			return results, err
   187  		}
   188  		addr, err := unit.PublicAddress()
   189  		if err != nil {
   190  			return results, errors.Annotatef(err, "error fetching address for unit %q", unit)
   191  		}
   192  		return params.PublicAddressResults{PublicAddress: addr.Value}, nil
   193  	}
   194  	return results, errors.Errorf("unknown unit or machine %q", p.Target)
   195  }
   196  
   197  // PrivateAddress implements the server side of Client.PrivateAddress.
   198  func (c *Client) PrivateAddress(p params.PrivateAddress) (results params.PrivateAddressResults, err error) {
   199  	if err := c.checkCanRead(); err != nil {
   200  		return params.PrivateAddressResults{}, err
   201  	}
   202  
   203  	switch {
   204  	case names.IsValidMachine(p.Target):
   205  		machine, err := c.api.stateAccessor.Machine(p.Target)
   206  		if err != nil {
   207  			return results, err
   208  		}
   209  		addr, err := machine.PrivateAddress()
   210  		if err != nil {
   211  			return results, errors.Annotatef(err, "error fetching address for machine %q", machine)
   212  		}
   213  		return params.PrivateAddressResults{PrivateAddress: addr.Value}, nil
   214  
   215  	case names.IsValidUnit(p.Target):
   216  		unit, err := c.api.stateAccessor.Unit(p.Target)
   217  		if err != nil {
   218  			return results, err
   219  		}
   220  		addr, err := unit.PrivateAddress()
   221  		if err != nil {
   222  			return results, errors.Annotatef(err, "error fetching address for unit %q", unit)
   223  		}
   224  		return params.PrivateAddressResults{PrivateAddress: addr.Value}, nil
   225  	}
   226  	return results, fmt.Errorf("unknown unit or machine %q", p.Target)
   227  
   228  }
   229  
   230  // GetModelConstraints returns the constraints for the model.
   231  func (c *Client) GetModelConstraints() (params.GetConstraintsResults, error) {
   232  	if err := c.checkCanRead(); err != nil {
   233  		return params.GetConstraintsResults{}, err
   234  	}
   235  
   236  	cons, err := c.api.stateAccessor.ModelConstraints()
   237  	if err != nil {
   238  		return params.GetConstraintsResults{}, err
   239  	}
   240  	return params.GetConstraintsResults{cons}, nil
   241  }
   242  
   243  // SetModelConstraints sets the constraints for the model.
   244  func (c *Client) SetModelConstraints(args params.SetConstraints) error {
   245  	if err := c.checkCanWrite(); err != nil {
   246  		return err
   247  	}
   248  
   249  	if err := c.check.ChangeAllowed(); err != nil {
   250  		return errors.Trace(err)
   251  	}
   252  	return c.api.stateAccessor.SetModelConstraints(args.Constraints)
   253  }
   254  
   255  // AddMachines adds new machines with the supplied parameters.
   256  func (c *Client) AddMachines(args params.AddMachines) (params.AddMachinesResults, error) {
   257  	if err := c.checkCanWrite(); err != nil {
   258  		return params.AddMachinesResults{}, err
   259  	}
   260  
   261  	return c.AddMachinesV2(args)
   262  }
   263  
   264  // AddMachinesV2 adds new machines with the supplied parameters.
   265  func (c *Client) AddMachinesV2(args params.AddMachines) (params.AddMachinesResults, error) {
   266  	results := params.AddMachinesResults{
   267  		Machines: make([]params.AddMachinesResult, len(args.MachineParams)),
   268  	}
   269  	if err := c.check.ChangeAllowed(); err != nil {
   270  		return results, errors.Trace(err)
   271  	}
   272  	for i, p := range args.MachineParams {
   273  		m, err := c.addOneMachine(p)
   274  		results.Machines[i].Error = common.ServerError(err)
   275  		if err == nil {
   276  			results.Machines[i].Machine = m.Id()
   277  		}
   278  	}
   279  	return results, nil
   280  }
   281  
   282  // InjectMachines injects a machine into state with provisioned status.
   283  func (c *Client) InjectMachines(args params.AddMachines) (params.AddMachinesResults, error) {
   284  	if err := c.checkCanWrite(); err != nil {
   285  		return params.AddMachinesResults{}, err
   286  	}
   287  
   288  	return c.AddMachines(args)
   289  }
   290  
   291  func (c *Client) addOneMachine(p params.AddMachineParams) (*state.Machine, error) {
   292  	if p.ParentId != "" && p.ContainerType == "" {
   293  		return nil, fmt.Errorf("parent machine specified without container type")
   294  	}
   295  	if p.ContainerType != "" && p.Placement != nil {
   296  		return nil, fmt.Errorf("container type and placement are mutually exclusive")
   297  	}
   298  	if p.Placement != nil {
   299  		// Extract container type and parent from container placement directives.
   300  		containerType, err := instance.ParseContainerType(p.Placement.Scope)
   301  		if err == nil {
   302  			p.ContainerType = containerType
   303  			p.ParentId = p.Placement.Directive
   304  			p.Placement = nil
   305  		}
   306  	}
   307  
   308  	if p.ContainerType != "" || p.Placement != nil {
   309  		// Guard against dubious client by making sure that
   310  		// the following attributes can only be set when we're
   311  		// not using placement.
   312  		p.InstanceId = ""
   313  		p.Nonce = ""
   314  		p.HardwareCharacteristics = instance.HardwareCharacteristics{}
   315  		p.Addrs = nil
   316  	}
   317  
   318  	if p.Series == "" {
   319  		conf, err := c.api.stateAccessor.ModelConfig()
   320  		if err != nil {
   321  			return nil, err
   322  		}
   323  		p.Series = config.PreferredSeries(conf)
   324  	}
   325  
   326  	var placementDirective string
   327  	if p.Placement != nil {
   328  		env, err := c.api.stateAccessor.Model()
   329  		if err != nil {
   330  			return nil, err
   331  		}
   332  		// For 1.21 we should support both UUID and name, and with 1.22
   333  		// just support UUID
   334  		if p.Placement.Scope != env.Name() && p.Placement.Scope != env.UUID() {
   335  			return nil, fmt.Errorf("invalid model name %q", p.Placement.Scope)
   336  		}
   337  		placementDirective = p.Placement.Directive
   338  	}
   339  
   340  	jobs, err := common.StateJobs(p.Jobs)
   341  	if err != nil {
   342  		return nil, err
   343  	}
   344  	template := state.MachineTemplate{
   345  		Series:      p.Series,
   346  		Constraints: p.Constraints,
   347  		InstanceId:  p.InstanceId,
   348  		Jobs:        jobs,
   349  		Nonce:       p.Nonce,
   350  		HardwareCharacteristics: p.HardwareCharacteristics,
   351  		Addresses:               params.NetworkAddresses(p.Addrs...),
   352  		Placement:               placementDirective,
   353  	}
   354  	if p.ContainerType == "" {
   355  		return c.api.stateAccessor.AddOneMachine(template)
   356  	}
   357  	if p.ParentId != "" {
   358  		return c.api.stateAccessor.AddMachineInsideMachine(template, p.ParentId, p.ContainerType)
   359  	}
   360  	return c.api.stateAccessor.AddMachineInsideNewMachine(template, template, p.ContainerType)
   361  }
   362  
   363  // ProvisioningScript returns a shell script that, when run,
   364  // provisions a machine agent on the machine executing the script.
   365  func (c *Client) ProvisioningScript(args params.ProvisioningScriptParams) (params.ProvisioningScriptResult, error) {
   366  	if err := c.checkCanWrite(); err != nil {
   367  		return params.ProvisioningScriptResult{}, err
   368  	}
   369  
   370  	var result params.ProvisioningScriptResult
   371  	icfg, err := InstanceConfig(c.api.state(), args.MachineId, args.Nonce, args.DataDir)
   372  	if err != nil {
   373  		return result, common.ServerError(errors.Annotate(
   374  			err, "getting instance config",
   375  		))
   376  	}
   377  
   378  	// Until DisablePackageCommands is retired, for backwards
   379  	// compatibility, we must respect the client's request and
   380  	// override any model settings the user may have specified.
   381  	// If the client does specify this setting, it will only ever be
   382  	// true. False indicates the client doesn't care and we should use
   383  	// what's specified in the environment config.
   384  	if args.DisablePackageCommands {
   385  		icfg.EnableOSRefreshUpdate = false
   386  		icfg.EnableOSUpgrade = false
   387  	} else if cfg, err := c.api.stateAccessor.ModelConfig(); err != nil {
   388  		return result, common.ServerError(errors.Annotate(
   389  			err, "getting model config",
   390  		))
   391  	} else {
   392  		icfg.EnableOSUpgrade = cfg.EnableOSUpgrade()
   393  		icfg.EnableOSRefreshUpdate = cfg.EnableOSRefreshUpdate()
   394  	}
   395  
   396  	result.Script, err = manual.ProvisioningScript(icfg)
   397  	if err != nil {
   398  		return result, common.ServerError(errors.Annotate(
   399  			err, "getting provisioning script",
   400  		))
   401  	}
   402  	return result, nil
   403  }
   404  
   405  // DestroyMachines removes a given set of machines.
   406  func (c *Client) DestroyMachines(args params.DestroyMachines) error {
   407  	if err := c.checkCanWrite(); err != nil {
   408  		return err
   409  	}
   410  
   411  	if err := c.check.RemoveAllowed(); !args.Force && err != nil {
   412  		return errors.Trace(err)
   413  	}
   414  
   415  	return common.DestroyMachines(c.api.stateAccessor, args.Force, args.MachineNames...)
   416  }
   417  
   418  // ModelInfo returns information about the current model.
   419  func (c *Client) ModelInfo() (params.ModelInfo, error) {
   420  	if err := c.checkCanWrite(); err != nil {
   421  		return params.ModelInfo{}, err
   422  	}
   423  	state := c.api.stateAccessor
   424  	conf, err := state.ModelConfig()
   425  	if err != nil {
   426  		return params.ModelInfo{}, err
   427  	}
   428  	model, err := state.Model()
   429  	if err != nil {
   430  		return params.ModelInfo{}, err
   431  	}
   432  	info := params.ModelInfo{
   433  		DefaultSeries: config.PreferredSeries(conf),
   434  		CloudTag:      names.NewCloudTag(model.Cloud()).String(),
   435  		CloudRegion:   model.CloudRegion(),
   436  		ProviderType:  conf.Type(),
   437  		Name:          conf.Name(),
   438  		UUID:          model.UUID(),
   439  		OwnerTag:      model.Owner().String(),
   440  		Life:          params.Life(model.Life().String()),
   441  	}
   442  	if tag, ok := model.CloudCredential(); ok {
   443  		info.CloudCredentialTag = tag.String()
   444  	}
   445  	return info, nil
   446  }
   447  
   448  func modelInfo(st *state.State, user permission.UserAccess) (params.ModelUserInfo, error) {
   449  	return common.ModelUserInfo(user, st)
   450  }
   451  
   452  // ModelUserInfo returns information on all users in the model.
   453  func (c *Client) ModelUserInfo() (params.ModelUserInfoResults, error) {
   454  	var results params.ModelUserInfoResults
   455  	if err := c.checkCanRead(); err != nil {
   456  		return results, err
   457  	}
   458  
   459  	env, err := c.api.stateAccessor.Model()
   460  	if err != nil {
   461  		return results, errors.Trace(err)
   462  	}
   463  	users, err := env.Users()
   464  	if err != nil {
   465  		return results, errors.Trace(err)
   466  	}
   467  
   468  	for _, user := range users {
   469  		var result params.ModelUserInfoResult
   470  		userInfo, err := modelInfo(c.api.state(), user)
   471  		if err != nil {
   472  			result.Error = common.ServerError(err)
   473  		} else {
   474  			result.Result = &userInfo
   475  		}
   476  		results.Results = append(results.Results, result)
   477  	}
   478  	return results, nil
   479  }
   480  
   481  // AgentVersion returns the current version that the API server is running.
   482  func (c *Client) AgentVersion() (params.AgentVersionResult, error) {
   483  	if err := c.checkCanRead(); err != nil {
   484  		return params.AgentVersionResult{}, err
   485  	}
   486  
   487  	return params.AgentVersionResult{Version: jujuversion.Current}, nil
   488  }
   489  
   490  // SetModelAgentVersion sets the model agent version.
   491  func (c *Client) SetModelAgentVersion(args params.SetModelAgentVersion) error {
   492  	if err := c.checkCanWrite(); err != nil {
   493  		return err
   494  	}
   495  
   496  	if err := c.check.ChangeAllowed(); err != nil {
   497  		return errors.Trace(err)
   498  	}
   499  	// Before changing the agent version to trigger an upgrade or downgrade,
   500  	// we'll do a very basic check to ensure the environment is accessible.
   501  	env, err := c.newEnviron()
   502  	if err != nil {
   503  		return errors.Trace(err)
   504  	}
   505  	if err := environs.CheckProviderAPI(env); err != nil {
   506  		return err
   507  	}
   508  	return c.api.stateAccessor.SetModelAgentVersion(args.Version)
   509  }
   510  
   511  // AbortCurrentUpgrade aborts and archives the current upgrade
   512  // synchronisation record, if any.
   513  func (c *Client) AbortCurrentUpgrade() error {
   514  	if err := c.checkCanWrite(); err != nil {
   515  		return err
   516  	}
   517  
   518  	if err := c.check.ChangeAllowed(); err != nil {
   519  		return errors.Trace(err)
   520  	}
   521  	return c.api.stateAccessor.AbortCurrentUpgrade()
   522  }
   523  
   524  // FindTools returns a List containing all tools matching the given parameters.
   525  func (c *Client) FindTools(args params.FindToolsParams) (params.FindToolsResult, error) {
   526  	if err := c.checkCanWrite(); err != nil {
   527  		return params.FindToolsResult{}, err
   528  	}
   529  
   530  	return c.api.toolsFinder.FindTools(args)
   531  }
   532  
   533  func (c *Client) AddCharm(args params.AddCharm) error {
   534  	if err := c.checkCanWrite(); err != nil {
   535  		return err
   536  	}
   537  
   538  	return application.AddCharmWithAuthorization(c.api.state(), params.AddCharmWithAuthorization{
   539  		URL:     args.URL,
   540  		Channel: args.Channel,
   541  	})
   542  }
   543  
   544  // AddCharmWithAuthorization adds the given charm URL (which must include revision) to
   545  // the model, if it does not exist yet. Local charms are not
   546  // supported, only charm store URLs. See also AddLocalCharm().
   547  //
   548  // The authorization macaroon, args.CharmStoreMacaroon, may be
   549  // omitted, in which case this call is equivalent to AddCharm.
   550  func (c *Client) AddCharmWithAuthorization(args params.AddCharmWithAuthorization) error {
   551  	if err := c.checkCanWrite(); err != nil {
   552  		return err
   553  	}
   554  
   555  	return application.AddCharmWithAuthorization(c.api.state(), args)
   556  }
   557  
   558  // ResolveCharm resolves the best available charm URLs with series, for charm
   559  // locations without a series specified.
   560  func (c *Client) ResolveCharms(args params.ResolveCharms) (params.ResolveCharmResults, error) {
   561  	if err := c.checkCanWrite(); err != nil {
   562  		return params.ResolveCharmResults{}, err
   563  	}
   564  
   565  	return application.ResolveCharms(c.api.state(), args)
   566  }
   567  
   568  // RetryProvisioning marks a provisioning error as transient on the machines.
   569  func (c *Client) RetryProvisioning(p params.Entities) (params.ErrorResults, error) {
   570  	if err := c.checkCanWrite(); err != nil {
   571  		return params.ErrorResults{}, err
   572  	}
   573  
   574  	if err := c.check.ChangeAllowed(); err != nil {
   575  		return params.ErrorResults{}, errors.Trace(err)
   576  	}
   577  	entityStatus := make([]params.EntityStatusArgs, len(p.Entities))
   578  	for i, entity := range p.Entities {
   579  		entityStatus[i] = params.EntityStatusArgs{Tag: entity.Tag, Data: map[string]interface{}{"transient": true}}
   580  	}
   581  	return c.api.statusSetter.UpdateStatus(params.SetStatus{
   582  		Entities: entityStatus,
   583  	})
   584  }
   585  
   586  // APIHostPorts returns the API host/port addresses stored in state.
   587  func (c *Client) APIHostPorts() (result params.APIHostPortsResult, err error) {
   588  	if err := c.checkCanWrite(); err != nil {
   589  		return result, err
   590  	}
   591  
   592  	var servers [][]network.HostPort
   593  	if servers, err = c.api.stateAccessor.APIHostPorts(); err != nil {
   594  		return params.APIHostPortsResult{}, err
   595  	}
   596  	result.Servers = params.FromNetworkHostsPorts(servers)
   597  	return result, nil
   598  }