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

     1  // Copyright 2013, 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package api
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/url"
    12  	"os"
    13  	"strings"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/loggo"
    17  	"github.com/juju/names"
    18  	"github.com/juju/version"
    19  	"golang.org/x/net/websocket"
    20  	"gopkg.in/juju/charm.v6-unstable"
    21  	csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
    22  	"gopkg.in/macaroon.v1"
    23  
    24  	"github.com/juju/juju/api/base"
    25  	"github.com/juju/juju/apiserver/params"
    26  	"github.com/juju/juju/constraints"
    27  	"github.com/juju/juju/network"
    28  	"github.com/juju/juju/tools"
    29  )
    30  
    31  // Client represents the client-accessible part of the state.
    32  type Client struct {
    33  	base.ClientFacade
    34  	facade base.FacadeCaller
    35  	st     *state
    36  }
    37  
    38  // Status returns the status of the juju model.
    39  func (c *Client) Status(patterns []string) (*params.FullStatus, error) {
    40  	var result params.FullStatus
    41  	p := params.StatusParams{Patterns: patterns}
    42  	if err := c.facade.FacadeCall("FullStatus", p, &result); err != nil {
    43  		return nil, err
    44  	}
    45  	return &result, nil
    46  }
    47  
    48  // StatusHistory retrieves the last <size> results of
    49  // <kind:combined|agent|workload|machine|machineinstance|container|containerinstance> status
    50  // for <name> unit
    51  func (c *Client) StatusHistory(kind params.HistoryKind, name string, size int) (*params.StatusHistoryResults, error) {
    52  	var results params.StatusHistoryResults
    53  	args := params.StatusHistoryArgs{
    54  		Kind: kind,
    55  		Size: size,
    56  		Name: name,
    57  	}
    58  	err := c.facade.FacadeCall("StatusHistory", args, &results)
    59  	if err != nil {
    60  		return &params.StatusHistoryResults{}, errors.Trace(err)
    61  	}
    62  	return &results, nil
    63  }
    64  
    65  // Resolved clears errors on a unit.
    66  func (c *Client) Resolved(unit string, retry bool) error {
    67  	p := params.Resolved{
    68  		UnitName: unit,
    69  		Retry:    retry,
    70  	}
    71  	return c.facade.FacadeCall("Resolved", p, nil)
    72  }
    73  
    74  // RetryProvisioning updates the provisioning status of a machine allowing the
    75  // provisioner to retry.
    76  func (c *Client) RetryProvisioning(machines ...names.MachineTag) ([]params.ErrorResult, error) {
    77  	p := params.Entities{}
    78  	p.Entities = make([]params.Entity, len(machines))
    79  	for i, machine := range machines {
    80  		p.Entities[i] = params.Entity{Tag: machine.String()}
    81  	}
    82  	var results params.ErrorResults
    83  	err := c.facade.FacadeCall("RetryProvisioning", p, &results)
    84  	return results.Results, err
    85  }
    86  
    87  // PublicAddress returns the public address of the specified
    88  // machine or unit. For a machine, target is an id not a tag.
    89  func (c *Client) PublicAddress(target string) (string, error) {
    90  	var results params.PublicAddressResults
    91  	p := params.PublicAddress{Target: target}
    92  	err := c.facade.FacadeCall("PublicAddress", p, &results)
    93  	return results.PublicAddress, err
    94  }
    95  
    96  // PrivateAddress returns the private address of the specified
    97  // machine or unit.
    98  func (c *Client) PrivateAddress(target string) (string, error) {
    99  	var results params.PrivateAddressResults
   100  	p := params.PrivateAddress{Target: target}
   101  	err := c.facade.FacadeCall("PrivateAddress", p, &results)
   102  	return results.PrivateAddress, err
   103  }
   104  
   105  // AddMachines adds new machines with the supplied parameters.
   106  func (c *Client) AddMachines(machineParams []params.AddMachineParams) ([]params.AddMachinesResult, error) {
   107  	args := params.AddMachines{
   108  		MachineParams: machineParams,
   109  	}
   110  	results := new(params.AddMachinesResults)
   111  	err := c.facade.FacadeCall("AddMachinesV2", args, results)
   112  	return results.Machines, err
   113  }
   114  
   115  // ProvisioningScript returns a shell script that, when run,
   116  // provisions a machine agent on the machine executing the script.
   117  func (c *Client) ProvisioningScript(args params.ProvisioningScriptParams) (script string, err error) {
   118  	var result params.ProvisioningScriptResult
   119  	if err = c.facade.FacadeCall("ProvisioningScript", args, &result); err != nil {
   120  		return "", err
   121  	}
   122  	return result.Script, nil
   123  }
   124  
   125  // DestroyMachines removes a given set of machines.
   126  func (c *Client) DestroyMachines(machines ...string) error {
   127  	params := params.DestroyMachines{MachineNames: machines}
   128  	return c.facade.FacadeCall("DestroyMachines", params, nil)
   129  }
   130  
   131  // ForceDestroyMachines removes a given set of machines and all associated units.
   132  func (c *Client) ForceDestroyMachines(machines ...string) error {
   133  	params := params.DestroyMachines{Force: true, MachineNames: machines}
   134  	return c.facade.FacadeCall("DestroyMachines", params, nil)
   135  }
   136  
   137  // GetModelConstraints returns the constraints for the model.
   138  func (c *Client) GetModelConstraints() (constraints.Value, error) {
   139  	results := new(params.GetConstraintsResults)
   140  	err := c.facade.FacadeCall("GetModelConstraints", nil, results)
   141  	return results.Constraints, err
   142  }
   143  
   144  // SetModelConstraints specifies the constraints for the model.
   145  func (c *Client) SetModelConstraints(constraints constraints.Value) error {
   146  	params := params.SetConstraints{
   147  		Constraints: constraints,
   148  	}
   149  	return c.facade.FacadeCall("SetModelConstraints", params, nil)
   150  }
   151  
   152  // CharmInfo holds information about a charm.
   153  type CharmInfo struct {
   154  	Revision int
   155  	URL      string
   156  	Config   *charm.Config
   157  	Meta     *charm.Meta
   158  	Actions  *charm.Actions
   159  }
   160  
   161  // CharmInfo returns information about the requested charm.
   162  func (c *Client) CharmInfo(charmURL string) (*CharmInfo, error) {
   163  	args := params.CharmInfo{CharmURL: charmURL}
   164  	info := new(CharmInfo)
   165  	if err := c.facade.FacadeCall("CharmInfo", args, info); err != nil {
   166  		return nil, err
   167  	}
   168  	return info, nil
   169  }
   170  
   171  // ModelInfo returns details about the Juju model.
   172  func (c *Client) ModelInfo() (params.ModelInfo, error) {
   173  	var info params.ModelInfo
   174  	err := c.facade.FacadeCall("ModelInfo", nil, &info)
   175  	return info, err
   176  }
   177  
   178  // ModelUUID returns the model UUID from the client connection.
   179  func (c *Client) ModelUUID() string {
   180  	tag, err := c.st.ModelTag()
   181  	if err != nil {
   182  		logger.Warningf("model tag not an model: %v", err)
   183  		return ""
   184  	}
   185  	return tag.Id()
   186  }
   187  
   188  // ModelUserInfo returns information on all users in the model.
   189  func (c *Client) ModelUserInfo() ([]params.ModelUserInfo, error) {
   190  	var results params.ModelUserInfoResults
   191  	err := c.facade.FacadeCall("ModelUserInfo", nil, &results)
   192  	if err != nil {
   193  		return nil, errors.Trace(err)
   194  	}
   195  
   196  	info := []params.ModelUserInfo{}
   197  	for i, result := range results.Results {
   198  		if result.Result == nil {
   199  			return nil, errors.Errorf("unexpected nil result at position %d", i)
   200  		}
   201  		info = append(info, *result.Result)
   202  	}
   203  	return info, nil
   204  }
   205  
   206  // WatchAll holds the id of the newly-created AllWatcher/AllModelWatcher.
   207  type WatchAll struct {
   208  	AllWatcherId string
   209  }
   210  
   211  // WatchAll returns an AllWatcher, from which you can request the Next
   212  // collection of Deltas.
   213  func (c *Client) WatchAll() (*AllWatcher, error) {
   214  	info := new(WatchAll)
   215  	if err := c.facade.FacadeCall("WatchAll", nil, info); err != nil {
   216  		return nil, err
   217  	}
   218  	return NewAllWatcher(c.st, &info.AllWatcherId), nil
   219  }
   220  
   221  // Close closes the Client's underlying State connection
   222  // Client is unique among the api.State facades in closing its own State
   223  // connection, but it is conventional to use a Client object without any access
   224  // to its underlying state connection.
   225  func (c *Client) Close() error {
   226  	return c.st.Close()
   227  }
   228  
   229  // ModelGet returns all model settings.
   230  func (c *Client) ModelGet() (map[string]interface{}, error) {
   231  	result := params.ModelConfigResults{}
   232  	err := c.facade.FacadeCall("ModelGet", nil, &result)
   233  	return result.Config, err
   234  }
   235  
   236  // ModelSet sets the given key-value pairs in the model.
   237  func (c *Client) ModelSet(config map[string]interface{}) error {
   238  	args := params.ModelSet{Config: config}
   239  	return c.facade.FacadeCall("ModelSet", args, nil)
   240  }
   241  
   242  // ModelUnset sets the given key-value pairs in the model.
   243  func (c *Client) ModelUnset(keys ...string) error {
   244  	args := params.ModelUnset{Keys: keys}
   245  	return c.facade.FacadeCall("ModelUnset", args, nil)
   246  }
   247  
   248  // SetModelAgentVersion sets the model agent-version setting
   249  // to the given value.
   250  func (c *Client) SetModelAgentVersion(version version.Number) error {
   251  	args := params.SetModelAgentVersion{Version: version}
   252  	return c.facade.FacadeCall("SetModelAgentVersion", args, nil)
   253  }
   254  
   255  // AbortCurrentUpgrade aborts and archives the current upgrade
   256  // synchronisation record, if any.
   257  func (c *Client) AbortCurrentUpgrade() error {
   258  	return c.facade.FacadeCall("AbortCurrentUpgrade", nil, nil)
   259  }
   260  
   261  // FindTools returns a List containing all tools matching the specified parameters.
   262  func (c *Client) FindTools(majorVersion, minorVersion int, series, arch string) (result params.FindToolsResult, err error) {
   263  	args := params.FindToolsParams{
   264  		MajorVersion: majorVersion,
   265  		MinorVersion: minorVersion,
   266  		Arch:         arch,
   267  		Series:       series,
   268  	}
   269  	err = c.facade.FacadeCall("FindTools", args, &result)
   270  	return result, err
   271  }
   272  
   273  // DestroyModel puts the model into a "dying" state,
   274  // and removes all non-manager machine instances. DestroyModel
   275  // will fail if there are any manually-provisioned non-manager machines
   276  // in state.
   277  func (c *Client) DestroyModel() error {
   278  	return c.facade.FacadeCall("DestroyModel", nil, nil)
   279  }
   280  
   281  // AddLocalCharm prepares the given charm with a local: schema in its
   282  // URL, and uploads it via the API server, returning the assigned
   283  // charm URL.
   284  func (c *Client) AddLocalCharm(curl *charm.URL, ch charm.Charm) (*charm.URL, error) {
   285  	if curl.Schema != "local" {
   286  		return nil, errors.Errorf("expected charm URL with local: schema, got %q", curl.String())
   287  	}
   288  
   289  	if err := c.validateCharmVersion(ch); err != nil {
   290  		return nil, errors.Trace(err)
   291  	}
   292  
   293  	// Package the charm for uploading.
   294  	var archive *os.File
   295  	switch ch := ch.(type) {
   296  	case *charm.CharmDir:
   297  		var err error
   298  		if archive, err = ioutil.TempFile("", "charm"); err != nil {
   299  			return nil, errors.Annotate(err, "cannot create temp file")
   300  		}
   301  		defer os.Remove(archive.Name())
   302  		defer archive.Close()
   303  		if err := ch.ArchiveTo(archive); err != nil {
   304  			return nil, errors.Annotate(err, "cannot repackage charm")
   305  		}
   306  		if _, err := archive.Seek(0, 0); err != nil {
   307  			return nil, errors.Annotate(err, "cannot rewind packaged charm")
   308  		}
   309  	case *charm.CharmArchive:
   310  		var err error
   311  		if archive, err = os.Open(ch.Path); err != nil {
   312  			return nil, errors.Annotate(err, "cannot read charm archive")
   313  		}
   314  		defer archive.Close()
   315  	default:
   316  		return nil, errors.Errorf("unknown charm type %T", ch)
   317  	}
   318  
   319  	curl, err := c.UploadCharm(curl, archive)
   320  	if err != nil {
   321  		return nil, errors.Trace(err)
   322  	}
   323  	return curl, nil
   324  }
   325  
   326  // UploadCharm sends the content to the API server using an HTTP post.
   327  func (c *Client) UploadCharm(curl *charm.URL, content io.ReadSeeker) (*charm.URL, error) {
   328  	endpoint := "/charms?series=" + curl.Series
   329  	contentType := "application/zip"
   330  	var resp params.CharmsResponse
   331  	if err := c.httpPost(content, endpoint, contentType, &resp); err != nil {
   332  		return nil, errors.Trace(err)
   333  	}
   334  
   335  	curl, err := charm.ParseURL(resp.CharmURL)
   336  	if err != nil {
   337  		return nil, errors.Annotatef(err, "bad charm URL in response")
   338  	}
   339  	return curl, nil
   340  }
   341  
   342  type minJujuVersionErr struct {
   343  	*errors.Err
   344  }
   345  
   346  func minVersionError(minver, jujuver version.Number) error {
   347  	err := errors.NewErr("charm's min version (%s) is higher than this juju environment's version (%s)",
   348  		minver, jujuver)
   349  	err.SetLocation(1)
   350  	return minJujuVersionErr{&err}
   351  }
   352  
   353  func (c *Client) validateCharmVersion(ch charm.Charm) error {
   354  	minver := ch.Meta().MinJujuVersion
   355  	if minver != version.Zero {
   356  		agentver, err := c.AgentVersion()
   357  		if err != nil {
   358  			return errors.Trace(err)
   359  		}
   360  
   361  		if minver.Compare(agentver) > 0 {
   362  			return minVersionError(minver, agentver)
   363  		}
   364  	}
   365  	return nil
   366  }
   367  
   368  // TODO(ericsnow) Use charmstore.CharmID for AddCharm() & AddCharmWithAuth().
   369  
   370  // AddCharm adds the given charm URL (which must include revision) to
   371  // the model, if it does not exist yet. Local charms are not
   372  // supported, only charm store URLs. See also AddLocalCharm() in the
   373  // client-side API.
   374  //
   375  // If the AddCharm API call fails because of an authorization error
   376  // when retrieving the charm from the charm store, an error
   377  // satisfying params.IsCodeUnauthorized will be returned.
   378  func (c *Client) AddCharm(curl *charm.URL, channel csparams.Channel) error {
   379  	args := params.AddCharm{
   380  		URL:     curl.String(),
   381  		Channel: string(channel),
   382  	}
   383  	if err := c.facade.FacadeCall("AddCharm", args, nil); err != nil {
   384  		return errors.Trace(err)
   385  	}
   386  	return nil
   387  }
   388  
   389  // AddCharmWithAuthorization is like AddCharm except it also provides
   390  // the given charmstore macaroon for the juju server to use when
   391  // obtaining the charm from the charm store. The macaroon is
   392  // conventionally obtained from the /delegatable-macaroon endpoint in
   393  // the charm store.
   394  //
   395  // If the AddCharmWithAuthorization API call fails because of an
   396  // authorization error when retrieving the charm from the charm store,
   397  // an error satisfying params.IsCodeUnauthorized will be returned.
   398  func (c *Client) AddCharmWithAuthorization(curl *charm.URL, channel csparams.Channel, csMac *macaroon.Macaroon) error {
   399  	args := params.AddCharmWithAuthorization{
   400  		URL:                curl.String(),
   401  		Channel:            string(channel),
   402  		CharmStoreMacaroon: csMac,
   403  	}
   404  	if err := c.facade.FacadeCall("AddCharmWithAuthorization", args, nil); err != nil {
   405  		return errors.Trace(err)
   406  	}
   407  	return nil
   408  }
   409  
   410  // ResolveCharm resolves the best available charm URLs with series, for charm
   411  // locations without a series specified.
   412  func (c *Client) ResolveCharm(ref *charm.URL) (*charm.URL, error) {
   413  	args := params.ResolveCharms{References: []charm.URL{*ref}}
   414  	result := new(params.ResolveCharmResults)
   415  	if err := c.facade.FacadeCall("ResolveCharms", args, result); err != nil {
   416  		return nil, err
   417  	}
   418  	if len(result.URLs) == 0 {
   419  		return nil, errors.New("unexpected empty response")
   420  	}
   421  	urlInfo := result.URLs[0]
   422  	if urlInfo.Error != "" {
   423  		return nil, errors.New(urlInfo.Error)
   424  	}
   425  	return urlInfo.URL, nil
   426  }
   427  
   428  // UploadTools uploads tools at the specified location to the API server over HTTPS.
   429  func (c *Client) UploadTools(r io.ReadSeeker, vers version.Binary, additionalSeries ...string) (tools.List, error) {
   430  	endpoint := fmt.Sprintf("/tools?binaryVersion=%s&series=%s", vers, strings.Join(additionalSeries, ","))
   431  	contentType := "application/x-tar-gz"
   432  	var resp params.ToolsResult
   433  	if err := c.httpPost(r, endpoint, contentType, &resp); err != nil {
   434  		return nil, errors.Trace(err)
   435  	}
   436  	return resp.ToolsList, nil
   437  }
   438  
   439  func (c *Client) httpPost(content io.ReadSeeker, endpoint, contentType string, response interface{}) error {
   440  	req, err := http.NewRequest("POST", endpoint, nil)
   441  	if err != nil {
   442  		return errors.Annotate(err, "cannot create upload request")
   443  	}
   444  	req.Header.Set("Content-Type", contentType)
   445  
   446  	// The returned httpClient sets the base url to /model/<uuid> if it can.
   447  	httpClient, err := c.st.HTTPClient()
   448  	if err != nil {
   449  		return errors.Trace(err)
   450  	}
   451  
   452  	if err := httpClient.Do(req, content, response); err != nil {
   453  		return errors.Trace(err)
   454  	}
   455  	return nil
   456  }
   457  
   458  // APIHostPorts returns a slice of network.HostPort for each API server.
   459  func (c *Client) APIHostPorts() ([][]network.HostPort, error) {
   460  	var result params.APIHostPortsResult
   461  	if err := c.facade.FacadeCall("APIHostPorts", nil, &result); err != nil {
   462  		return nil, err
   463  	}
   464  	return result.NetworkHostsPorts(), nil
   465  }
   466  
   467  // AgentVersion reports the version number of the api server.
   468  func (c *Client) AgentVersion() (version.Number, error) {
   469  	var result params.AgentVersionResult
   470  	if err := c.facade.FacadeCall("AgentVersion", nil, &result); err != nil {
   471  		return version.Number{}, err
   472  	}
   473  	return result.Version, nil
   474  }
   475  
   476  // websocketDialConfig is called instead of websocket.DialConfig so we can
   477  // override it in tests.
   478  var websocketDialConfig = func(config *websocket.Config) (base.Stream, error) {
   479  	c, err := websocket.DialConfig(config)
   480  	if err != nil {
   481  		return nil, errors.Trace(err)
   482  	}
   483  	return websocketStream{c}, nil
   484  }
   485  
   486  type websocketStream struct {
   487  	*websocket.Conn
   488  }
   489  
   490  func (c websocketStream) ReadJSON(v interface{}) error {
   491  	return websocket.JSON.Receive(c.Conn, v)
   492  }
   493  
   494  func (c websocketStream) WriteJSON(v interface{}) error {
   495  	return websocket.JSON.Send(c.Conn, v)
   496  }
   497  
   498  // DebugLogParams holds parameters for WatchDebugLog that control the
   499  // filtering of the log messages. If the structure is zero initialized, the
   500  // entire log file is sent back starting from the end, and until the user
   501  // closes the connection.
   502  type DebugLogParams struct {
   503  	// IncludeEntity lists entity tags to include in the response. Tags may
   504  	// finish with a '*' to match a prefix e.g.: unit-mysql-*, machine-2. If
   505  	// none are set, then all lines are considered included.
   506  	IncludeEntity []string
   507  	// IncludeModule lists logging modules to include in the response. If none
   508  	// are set all modules are considered included.  If a module is specified,
   509  	// all the submodules also match.
   510  	IncludeModule []string
   511  	// ExcludeEntity lists entity tags to exclude from the response. As with
   512  	// IncludeEntity the values may finish with a '*'.
   513  	ExcludeEntity []string
   514  	// ExcludeModule lists logging modules to exclude from the resposne. If a
   515  	// module is specified, all the submodules are also excluded.
   516  	ExcludeModule []string
   517  	// Limit defines the maximum number of lines to return. Once this many
   518  	// have been sent, the socket is closed.  If zero, all filtered lines are
   519  	// sent down the connection until the client closes the connection.
   520  	Limit uint
   521  	// Backlog tells the server to try to go back this many lines before
   522  	// starting filtering. If backlog is zero and replay is false, then there
   523  	// may be an initial delay until the next matching log message is written.
   524  	Backlog uint
   525  	// Level specifies the minimum logging level to be sent back in the response.
   526  	Level loggo.Level
   527  	// Replay tells the server to start at the start of the log file rather
   528  	// than the end. If replay is true, backlog is ignored.
   529  	Replay bool
   530  	// NoTail tells the server to only return the logs it has now, and not
   531  	// to wait for new logs to arrive.
   532  	NoTail bool
   533  }
   534  
   535  // WatchDebugLog returns a ReadCloser that the caller can read the log
   536  // lines from. Only log lines that match the filtering specified in
   537  // the DebugLogParams are returned. It returns an error that satisfies
   538  // errors.IsNotImplemented when the API server does not support the
   539  // end-point.
   540  func (c *Client) WatchDebugLog(args DebugLogParams) (io.ReadCloser, error) {
   541  	// The websocket connection just hangs if the server doesn't have the log
   542  	// end point. So do a version check, as version was added at the same time
   543  	// as the remote end point.
   544  	_, err := c.AgentVersion()
   545  	if err != nil {
   546  		return nil, errors.NotSupportedf("WatchDebugLog")
   547  	}
   548  	// Prepare URL query attributes.
   549  	attrs := url.Values{
   550  		"includeEntity": args.IncludeEntity,
   551  		"includeModule": args.IncludeModule,
   552  		"excludeEntity": args.ExcludeEntity,
   553  		"excludeModule": args.ExcludeModule,
   554  	}
   555  	if args.Replay {
   556  		attrs.Set("replay", fmt.Sprint(args.Replay))
   557  	}
   558  	if args.NoTail {
   559  		attrs.Set("noTail", fmt.Sprint(args.NoTail))
   560  	}
   561  	if args.Limit > 0 {
   562  		attrs.Set("maxLines", fmt.Sprint(args.Limit))
   563  	}
   564  	if args.Backlog > 0 {
   565  		attrs.Set("backlog", fmt.Sprint(args.Backlog))
   566  	}
   567  	if args.Level != loggo.UNSPECIFIED {
   568  		attrs.Set("level", fmt.Sprint(args.Level))
   569  	}
   570  
   571  	connection, err := c.st.ConnectStream("/log", attrs)
   572  	if err != nil {
   573  		return nil, errors.Trace(err)
   574  	}
   575  	return connection, nil
   576  }