github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/state/api/client.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package api
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"os"
    12  	"time"
    13  
    14  	"launchpad.net/juju-core/charm"
    15  	"launchpad.net/juju-core/constraints"
    16  	"launchpad.net/juju-core/instance"
    17  	"launchpad.net/juju-core/state/api/params"
    18  	"launchpad.net/juju-core/utils"
    19  	"launchpad.net/juju-core/version"
    20  )
    21  
    22  // Client represents the client-accessible part of the state.
    23  type Client struct {
    24  	st *State
    25  }
    26  
    27  // MachineStatus holds status info about a machine.
    28  type MachineStatus struct {
    29  	Err            error
    30  	AgentState     params.Status
    31  	AgentStateInfo string
    32  	AgentVersion   string
    33  	DNSName        string
    34  	InstanceId     instance.Id
    35  	InstanceState  string
    36  	Life           string
    37  	Series         string
    38  	Id             string
    39  	Containers     map[string]MachineStatus
    40  	Hardware       string
    41  }
    42  
    43  // ServiceStatus holds status info about a service.
    44  type ServiceStatus struct {
    45  	Err           error
    46  	Charm         string
    47  	Exposed       bool
    48  	Life          string
    49  	Relations     map[string][]string
    50  	CanUpgradeTo  string
    51  	SubordinateTo []string
    52  	Units         map[string]UnitStatus
    53  }
    54  
    55  // UnitStatus holds status info about a unit.
    56  type UnitStatus struct {
    57  	Err            error
    58  	AgentState     params.Status
    59  	AgentStateInfo string
    60  	AgentVersion   string
    61  	Life           string
    62  	Machine        string
    63  	OpenedPorts    []string
    64  	PublicAddress  string
    65  	Charm          string
    66  	Subordinates   map[string]UnitStatus
    67  }
    68  
    69  // Status holds information about the status of a juju environment.
    70  type Status struct {
    71  	EnvironmentName string
    72  	Machines        map[string]MachineStatus
    73  	Services        map[string]ServiceStatus
    74  }
    75  
    76  // Status returns the status of the juju environment.
    77  func (c *Client) Status(patterns []string) (*Status, error) {
    78  	var result Status
    79  	p := params.StatusParams{Patterns: patterns}
    80  	if err := c.st.Call("Client", "", "FullStatus", p, &result); err != nil {
    81  		return nil, err
    82  	}
    83  	return &result, nil
    84  }
    85  
    86  // LegacyMachineStatus holds just the instance-id of a machine.
    87  type LegacyMachineStatus struct {
    88  	InstanceId string // Not type instance.Id just to match original api.
    89  }
    90  
    91  // LegacyStatus holds minimal information on the status of a juju environment.
    92  type LegacyStatus struct {
    93  	Machines map[string]LegacyMachineStatus
    94  }
    95  
    96  // LegacyStatus is a stub version of Status that 1.16 introduced. Should be
    97  // removed along with structs when api versioning makes it safe to do so.
    98  func (c *Client) LegacyStatus() (*LegacyStatus, error) {
    99  	var result LegacyStatus
   100  	if err := c.st.Call("Client", "", "Status", nil, &result); err != nil {
   101  		return nil, err
   102  	}
   103  	return &result, nil
   104  }
   105  
   106  // ServiceSet sets configuration options on a service.
   107  func (c *Client) ServiceSet(service string, options map[string]string) error {
   108  	p := params.ServiceSet{
   109  		ServiceName: service,
   110  		Options:     options,
   111  	}
   112  	// TODO(Nate): Put this back to ServiceSet when the GUI stops expecting
   113  	// ServiceSet to unset values set to an empty string.
   114  	return c.st.Call("Client", "", "NewServiceSetForClientAPI", p, nil)
   115  }
   116  
   117  // ServiceUnset resets configuration options on a service.
   118  func (c *Client) ServiceUnset(service string, options []string) error {
   119  	p := params.ServiceUnset{
   120  		ServiceName: service,
   121  		Options:     options,
   122  	}
   123  	return c.st.Call("Client", "", "ServiceUnset", p, nil)
   124  }
   125  
   126  // Resolved clears errors on a unit.
   127  func (c *Client) Resolved(unit string, retry bool) error {
   128  	p := params.Resolved{
   129  		UnitName: unit,
   130  		Retry:    retry,
   131  	}
   132  	return c.st.Call("Client", "", "Resolved", p, nil)
   133  }
   134  
   135  // PublicAddress returns the public address of the specified
   136  // machine or unit.
   137  func (c *Client) PublicAddress(target string) (string, error) {
   138  	var results params.PublicAddressResults
   139  	p := params.PublicAddress{Target: target}
   140  	err := c.st.Call("Client", "", "PublicAddress", p, &results)
   141  	return results.PublicAddress, err
   142  }
   143  
   144  // ServiceSetYAML sets configuration options on a service
   145  // given options in YAML format.
   146  func (c *Client) ServiceSetYAML(service string, yaml string) error {
   147  	p := params.ServiceSetYAML{
   148  		ServiceName: service,
   149  		Config:      yaml,
   150  	}
   151  	return c.st.Call("Client", "", "ServiceSetYAML", p, nil)
   152  }
   153  
   154  // ServiceGet returns the configuration for the named service.
   155  func (c *Client) ServiceGet(service string) (*params.ServiceGetResults, error) {
   156  	var results params.ServiceGetResults
   157  	params := params.ServiceGet{ServiceName: service}
   158  	err := c.st.Call("Client", "", "ServiceGet", params, &results)
   159  	return &results, err
   160  }
   161  
   162  // AddRelation adds a relation between the specified endpoints and returns the relation info.
   163  func (c *Client) AddRelation(endpoints ...string) (*params.AddRelationResults, error) {
   164  	var addRelRes params.AddRelationResults
   165  	params := params.AddRelation{Endpoints: endpoints}
   166  	err := c.st.Call("Client", "", "AddRelation", params, &addRelRes)
   167  	return &addRelRes, err
   168  }
   169  
   170  // DestroyRelation removes the relation between the specified endpoints.
   171  func (c *Client) DestroyRelation(endpoints ...string) error {
   172  	params := params.DestroyRelation{Endpoints: endpoints}
   173  	return c.st.Call("Client", "", "DestroyRelation", params, nil)
   174  }
   175  
   176  // ServiceCharmRelations returns the service's charms relation names.
   177  func (c *Client) ServiceCharmRelations(service string) ([]string, error) {
   178  	var results params.ServiceCharmRelationsResults
   179  	params := params.ServiceCharmRelations{ServiceName: service}
   180  	err := c.st.Call("Client", "", "ServiceCharmRelations", params, &results)
   181  	return results.CharmRelations, err
   182  }
   183  
   184  // AddMachines adds new machines with the supplied parameters.
   185  func (c *Client) AddMachines(machineParams []params.AddMachineParams) ([]params.AddMachinesResult, error) {
   186  	args := params.AddMachines{
   187  		MachineParams: machineParams,
   188  	}
   189  	results := new(params.AddMachinesResults)
   190  	err := c.st.Call("Client", "", "AddMachines", args, results)
   191  	return results.Machines, err
   192  }
   193  
   194  // ProvisioningScript returns a shell script that, when run,
   195  // provisions a machine agent on the machine executing the script.
   196  func (c *Client) ProvisioningScript(args params.ProvisioningScriptParams) (script string, err error) {
   197  	var result params.ProvisioningScriptResult
   198  	if err = c.st.Call("Client", "", "ProvisioningScript", args, &result); err != nil {
   199  		return "", err
   200  	}
   201  	return result.Script, nil
   202  }
   203  
   204  // DestroyMachines removes a given set of machines.
   205  func (c *Client) DestroyMachines(machines ...string) error {
   206  	params := params.DestroyMachines{MachineNames: machines}
   207  	return c.st.Call("Client", "", "DestroyMachines", params, nil)
   208  }
   209  
   210  // ForceDestroyMachines removes a given set of machines and all associated units.
   211  func (c *Client) ForceDestroyMachines(machines ...string) error {
   212  	params := params.DestroyMachines{Force: true, MachineNames: machines}
   213  	return c.st.Call("Client", "", "DestroyMachines", params, nil)
   214  }
   215  
   216  // ServiceExpose changes the juju-managed firewall to expose any ports that
   217  // were also explicitly marked by units as open.
   218  func (c *Client) ServiceExpose(service string) error {
   219  	params := params.ServiceExpose{ServiceName: service}
   220  	return c.st.Call("Client", "", "ServiceExpose", params, nil)
   221  }
   222  
   223  // ServiceUnexpose changes the juju-managed firewall to unexpose any ports that
   224  // were also explicitly marked by units as open.
   225  func (c *Client) ServiceUnexpose(service string) error {
   226  	params := params.ServiceUnexpose{ServiceName: service}
   227  	return c.st.Call("Client", "", "ServiceUnexpose", params, nil)
   228  }
   229  
   230  // ServiceDeploy obtains the charm, either locally or from the charm store,
   231  // and deploys it.
   232  func (c *Client) ServiceDeploy(charmUrl string, serviceName string, numUnits int, configYAML string, cons constraints.Value, toMachineSpec string) error {
   233  	params := params.ServiceDeploy{
   234  		ServiceName:   serviceName,
   235  		CharmUrl:      charmUrl,
   236  		NumUnits:      numUnits,
   237  		ConfigYAML:    configYAML,
   238  		Constraints:   cons,
   239  		ToMachineSpec: toMachineSpec,
   240  	}
   241  	return c.st.Call("Client", "", "ServiceDeploy", params, nil)
   242  }
   243  
   244  // ServiceUpdate updates the service attributes, including charm URL,
   245  // minimum number of units, settings and constraints.
   246  // TODO(frankban) deprecate redundant API calls that this supercedes.
   247  func (c *Client) ServiceUpdate(args params.ServiceUpdate) error {
   248  	return c.st.Call("Client", "", "ServiceUpdate", args, nil)
   249  }
   250  
   251  // ServiceSetCharm sets the charm for a given service.
   252  func (c *Client) ServiceSetCharm(serviceName string, charmUrl string, force bool) error {
   253  	args := params.ServiceSetCharm{
   254  		ServiceName: serviceName,
   255  		CharmUrl:    charmUrl,
   256  		Force:       force,
   257  	}
   258  	return c.st.Call("Client", "", "ServiceSetCharm", args, nil)
   259  }
   260  
   261  // ServiceGetCharmURL returns the charm URL the given service is
   262  // running at present.
   263  func (c *Client) ServiceGetCharmURL(serviceName string) (*charm.URL, error) {
   264  	result := new(params.StringResult)
   265  	args := params.ServiceGet{ServiceName: serviceName}
   266  	err := c.st.Call("Client", "", "ServiceGetCharmURL", args, &result)
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  	return charm.ParseURL(result.Result)
   271  }
   272  
   273  // AddServiceUnits adds a given number of units to a service.
   274  func (c *Client) AddServiceUnits(service string, numUnits int, machineSpec string) ([]string, error) {
   275  	args := params.AddServiceUnits{
   276  		ServiceName:   service,
   277  		NumUnits:      numUnits,
   278  		ToMachineSpec: machineSpec,
   279  	}
   280  	results := new(params.AddServiceUnitsResults)
   281  	err := c.st.Call("Client", "", "AddServiceUnits", args, results)
   282  	return results.Units, err
   283  }
   284  
   285  // DestroyServiceUnits decreases the number of units dedicated to a service.
   286  func (c *Client) DestroyServiceUnits(unitNames ...string) error {
   287  	params := params.DestroyServiceUnits{unitNames}
   288  	return c.st.Call("Client", "", "DestroyServiceUnits", params, nil)
   289  }
   290  
   291  // ServiceDestroy destroys a given service.
   292  func (c *Client) ServiceDestroy(service string) error {
   293  	params := params.ServiceDestroy{
   294  		ServiceName: service,
   295  	}
   296  	return c.st.Call("Client", "", "ServiceDestroy", params, nil)
   297  }
   298  
   299  // GetServiceConstraints returns the constraints for the given service.
   300  func (c *Client) GetServiceConstraints(service string) (constraints.Value, error) {
   301  	results := new(params.GetConstraintsResults)
   302  	err := c.st.Call("Client", "", "GetServiceConstraints", params.GetServiceConstraints{service}, results)
   303  	return results.Constraints, err
   304  }
   305  
   306  // GetEnvironmentConstraints returns the constraints for the environment.
   307  func (c *Client) GetEnvironmentConstraints() (constraints.Value, error) {
   308  	results := new(params.GetConstraintsResults)
   309  	err := c.st.Call("Client", "", "GetEnvironmentConstraints", nil, results)
   310  	return results.Constraints, err
   311  }
   312  
   313  // SetServiceConstraints specifies the constraints for the given service.
   314  func (c *Client) SetServiceConstraints(service string, constraints constraints.Value) error {
   315  	params := params.SetConstraints{
   316  		ServiceName: service,
   317  		Constraints: constraints,
   318  	}
   319  	return c.st.Call("Client", "", "SetServiceConstraints", params, nil)
   320  }
   321  
   322  // SetEnvironmentConstraints specifies the constraints for the environment.
   323  func (c *Client) SetEnvironmentConstraints(constraints constraints.Value) error {
   324  	params := params.SetConstraints{
   325  		Constraints: constraints,
   326  	}
   327  	return c.st.Call("Client", "", "SetEnvironmentConstraints", params, nil)
   328  }
   329  
   330  // CharmInfo holds information about a charm.
   331  type CharmInfo struct {
   332  	Revision int
   333  	URL      string
   334  	Config   *charm.Config
   335  	Meta     *charm.Meta
   336  }
   337  
   338  // CharmInfo returns information about the requested charm.
   339  func (c *Client) CharmInfo(charmURL string) (*CharmInfo, error) {
   340  	args := params.CharmInfo{CharmURL: charmURL}
   341  	info := new(CharmInfo)
   342  	if err := c.st.Call("Client", "", "CharmInfo", args, info); err != nil {
   343  		return nil, err
   344  	}
   345  	return info, nil
   346  }
   347  
   348  // EnvironmentInfo holds information about the Juju environment.
   349  type EnvironmentInfo struct {
   350  	DefaultSeries string
   351  	ProviderType  string
   352  	Name          string
   353  	UUID          string
   354  }
   355  
   356  // EnvironmentInfo returns details about the Juju environment.
   357  func (c *Client) EnvironmentInfo() (*EnvironmentInfo, error) {
   358  	info := new(EnvironmentInfo)
   359  	err := c.st.Call("Client", "", "EnvironmentInfo", nil, info)
   360  	return info, err
   361  }
   362  
   363  // WatchAll holds the id of the newly-created AllWatcher.
   364  type WatchAll struct {
   365  	AllWatcherId string
   366  }
   367  
   368  // WatchAll returns an AllWatcher, from which you can request the Next
   369  // collection of Deltas.
   370  func (c *Client) WatchAll() (*AllWatcher, error) {
   371  	info := new(WatchAll)
   372  	if err := c.st.Call("Client", "", "WatchAll", nil, info); err != nil {
   373  		return nil, err
   374  	}
   375  	return newAllWatcher(c, &info.AllWatcherId), nil
   376  }
   377  
   378  // GetAnnotations returns annotations that have been set on the given entity.
   379  func (c *Client) GetAnnotations(tag string) (map[string]string, error) {
   380  	args := params.GetAnnotations{tag}
   381  	ann := new(params.GetAnnotationsResults)
   382  	err := c.st.Call("Client", "", "GetAnnotations", args, ann)
   383  	return ann.Annotations, err
   384  }
   385  
   386  // SetAnnotations sets the annotation pairs on the given entity.
   387  // Currently annotations are supported on machines, services,
   388  // units and the environment itself.
   389  func (c *Client) SetAnnotations(tag string, pairs map[string]string) error {
   390  	args := params.SetAnnotations{tag, pairs}
   391  	return c.st.Call("Client", "", "SetAnnotations", args, nil)
   392  }
   393  
   394  // Close closes the Client's underlying State connection
   395  // Client is unique among the api.State facades in closing its own State
   396  // connection, but it is conventional to use a Client object without any access
   397  // to its underlying state connection.
   398  func (c *Client) Close() error {
   399  	return c.st.Close()
   400  }
   401  
   402  // EnvironmentGet returns all environment settings.
   403  func (c *Client) EnvironmentGet() (map[string]interface{}, error) {
   404  	result := params.EnvironmentGetResults{}
   405  	err := c.st.Call("Client", "", "EnvironmentGet", nil, &result)
   406  	return result.Config, err
   407  }
   408  
   409  // EnvironmentSet sets the given key-value pairs in the environment.
   410  func (c *Client) EnvironmentSet(config map[string]interface{}) error {
   411  	args := params.EnvironmentSet{Config: config}
   412  	return c.st.Call("Client", "", "EnvironmentSet", args, nil)
   413  }
   414  
   415  // SetEnvironAgentVersion sets the environment agent-version setting
   416  // to the given value.
   417  func (c *Client) SetEnvironAgentVersion(version version.Number) error {
   418  	args := params.SetEnvironAgentVersion{Version: version}
   419  	return c.st.Call("Client", "", "SetEnvironAgentVersion", args, nil)
   420  }
   421  
   422  // FindTools returns a List containing all tools matching the specified parameters.
   423  func (c *Client) FindTools(majorVersion, minorVersion int,
   424  	series, arch string) (result params.FindToolsResults, err error) {
   425  
   426  	args := params.FindToolsParams{
   427  		MajorVersion: majorVersion,
   428  		MinorVersion: minorVersion,
   429  		Arch:         arch,
   430  		Series:       series,
   431  	}
   432  	err = c.st.Call("Client", "", "FindTools", args, &result)
   433  	return result, err
   434  }
   435  
   436  // RunOnAllMachines runs the command on all the machines with the specified
   437  // timeout.
   438  func (c *Client) RunOnAllMachines(commands string, timeout time.Duration) ([]params.RunResult, error) {
   439  	var results params.RunResults
   440  	args := params.RunParams{Commands: commands, Timeout: timeout}
   441  	err := c.st.Call("Client", "", "RunOnAllMachines", args, &results)
   442  	return results.Results, err
   443  }
   444  
   445  // Run the Commands specified on the machines identified through the ids
   446  // provided in the machines, services and units slices.
   447  func (c *Client) Run(run params.RunParams) ([]params.RunResult, error) {
   448  	var results params.RunResults
   449  	err := c.st.Call("Client", "", "Run", run, &results)
   450  	return results.Results, err
   451  }
   452  
   453  // DestroyEnvironment puts the environment into a "dying" state,
   454  // and removes all non-manager machine instances. DestroyEnvironment
   455  // will fail if there are any manually-provisioned non-manager machines
   456  // in state.
   457  func (c *Client) DestroyEnvironment() error {
   458  	return c.st.Call("Client", "", "DestroyEnvironment", nil, nil)
   459  }
   460  
   461  // AddLocalCharm prepares the given charm with a local: schema in its
   462  // URL, and uploads it via the API server, returning the assigned
   463  // charm URL. If the API server does not support charm uploads, an
   464  // error satisfying params.IsCodeNotImplemented() is returned.
   465  func (c *Client) AddLocalCharm(curl *charm.URL, ch charm.Charm) (*charm.URL, error) {
   466  	if curl.Schema != "local" {
   467  		return nil, fmt.Errorf("expected charm URL with local: schema, got %q", curl.String())
   468  	}
   469  	// Package the charm for uploading.
   470  	var archive *os.File
   471  	switch ch := ch.(type) {
   472  	case *charm.Dir:
   473  		var err error
   474  		if archive, err = ioutil.TempFile("", "charm"); err != nil {
   475  			return nil, fmt.Errorf("cannot create temp file: %v", err)
   476  		}
   477  		defer os.Remove(archive.Name())
   478  		defer archive.Close()
   479  		if err := ch.BundleTo(archive); err != nil {
   480  			return nil, fmt.Errorf("cannot repackage charm: %v", err)
   481  		}
   482  		if _, err := archive.Seek(0, 0); err != nil {
   483  			return nil, fmt.Errorf("cannot rewind packaged charm: %v", err)
   484  		}
   485  	case *charm.Bundle:
   486  		var err error
   487  		if archive, err = os.Open(ch.Path); err != nil {
   488  			return nil, fmt.Errorf("cannot read charm archive: %v", err)
   489  		}
   490  		defer archive.Close()
   491  	default:
   492  		return nil, fmt.Errorf("unknown charm type %T", ch)
   493  	}
   494  
   495  	// Prepare the upload request.
   496  	url := fmt.Sprintf("%s/charms?series=%s", c.st.serverRoot, curl.Series)
   497  	req, err := http.NewRequest("POST", url, archive)
   498  	if err != nil {
   499  		return nil, fmt.Errorf("cannot create upload request: %v", err)
   500  	}
   501  	req.SetBasicAuth(c.st.tag, c.st.password)
   502  	req.Header.Set("Content-Type", "application/zip")
   503  
   504  	// Send the request.
   505  
   506  	// BUG(dimitern) 2013-12-17 bug #1261780
   507  	// Due to issues with go 1.1.2, fixed later, we cannot use a
   508  	// regular TLS client with the CACert here, because we get "x509:
   509  	// cannot validate certificate for 127.0.0.1 because it doesn't
   510  	// contain any IP SANs". Once we use a later go version, this
   511  	// should be changed to connect to the API server with a regular
   512  	// HTTP+TLS enabled client, using the CACert (possily cached, like
   513  	// the tag and password) passed in api.Open()'s info argument.
   514  	resp, err := utils.GetNonValidatingHTTPClient().Do(req)
   515  	if err != nil {
   516  		return nil, fmt.Errorf("cannot upload charm: %v", err)
   517  	}
   518  	if resp.StatusCode == http.StatusMethodNotAllowed {
   519  		// API server is 1.16 or older, so charm upload
   520  		// is not supported; notify the client.
   521  		return nil, &params.Error{
   522  			Message: "charm upload is not supported by the API server",
   523  			Code:    params.CodeNotImplemented,
   524  		}
   525  	}
   526  
   527  	// Now parse the response & return.
   528  	body, err := ioutil.ReadAll(resp.Body)
   529  	if err != nil {
   530  		return nil, fmt.Errorf("cannot read charm upload response: %v", err)
   531  	}
   532  	defer resp.Body.Close()
   533  	var jsonResponse params.CharmsResponse
   534  	if err := json.Unmarshal(body, &jsonResponse); err != nil {
   535  		return nil, fmt.Errorf("cannot unmarshal upload response: %v", err)
   536  	}
   537  	if jsonResponse.Error != "" {
   538  		return nil, fmt.Errorf("error uploading charm: %v", jsonResponse.Error)
   539  	}
   540  	return charm.MustParseURL(jsonResponse.CharmURL), nil
   541  }
   542  
   543  // AddCharm adds the given charm URL (which must include revision) to
   544  // the environment, if it does not exist yet. Local charms are not
   545  // supported, only charm store URLs. See also AddLocalCharm() in the
   546  // client-side API.
   547  func (c *Client) AddCharm(curl *charm.URL) error {
   548  	args := params.CharmURL{URL: curl.String()}
   549  	return c.st.Call("Client", "", "AddCharm", args, nil)
   550  }