github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/migrationtarget/client.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package migrationtarget
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/url"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/httprequest"
    17  	"github.com/juju/version"
    18  	"gopkg.in/juju/charm.v6"
    19  	"gopkg.in/juju/names.v2"
    20  
    21  	"github.com/juju/juju/api/base"
    22  	"github.com/juju/juju/apiserver/params"
    23  	coremigration "github.com/juju/juju/core/migration"
    24  	"github.com/juju/juju/resource"
    25  	"github.com/juju/juju/tools"
    26  	jujuversion "github.com/juju/juju/version"
    27  )
    28  
    29  // NewClient returns a new Client based on an existing API connection.
    30  func NewClient(caller base.APICaller) *Client {
    31  	return &Client{
    32  		caller:            base.NewFacadeCaller(caller, "MigrationTarget"),
    33  		httpClientFactory: caller.HTTPClient,
    34  	}
    35  }
    36  
    37  // Client is the client-side API for the MigrationTarget facade. It is
    38  // used by the migrationmaster worker when talking to the target
    39  // controller during a migration.
    40  type Client struct {
    41  	caller            base.FacadeCaller
    42  	httpClientFactory func() (*httprequest.Client, error)
    43  }
    44  
    45  func (c *Client) Prechecks(model coremigration.ModelInfo) error {
    46  	args := params.MigrationModelInfo{
    47  		UUID:                   model.UUID,
    48  		Name:                   model.Name,
    49  		OwnerTag:               model.Owner.String(),
    50  		AgentVersion:           model.AgentVersion,
    51  		ControllerAgentVersion: model.ControllerAgentVersion,
    52  	}
    53  	return c.caller.FacadeCall("Prechecks", args, nil)
    54  }
    55  
    56  // Import takes a serialized model and imports it into the target
    57  // controller.
    58  func (c *Client) Import(bytes []byte) error {
    59  	serialized := params.SerializedModel{Bytes: bytes}
    60  	return c.caller.FacadeCall("Import", serialized, nil)
    61  }
    62  
    63  // Abort removes all data relating to a previously imported model.
    64  func (c *Client) Abort(modelUUID string) error {
    65  	args := params.ModelArgs{ModelTag: names.NewModelTag(modelUUID).String()}
    66  	return c.caller.FacadeCall("Abort", args, nil)
    67  }
    68  
    69  // Activate marks a migrated model as being ready to use.
    70  func (c *Client) Activate(modelUUID string) error {
    71  	args := params.ModelArgs{ModelTag: names.NewModelTag(modelUUID).String()}
    72  	return c.caller.FacadeCall("Activate", args, nil)
    73  }
    74  
    75  // UploadCharm sends the content to the API server using an HTTP post in order
    76  // to add the charm binary to the model specified.
    77  func (c *Client) UploadCharm(modelUUID string, curl *charm.URL, content io.ReadSeeker) (*charm.URL, error) {
    78  	args := url.Values{}
    79  	args.Add("schema", curl.Schema)
    80  	args.Add("user", curl.User)
    81  	args.Add("series", curl.Series)
    82  	args.Add("revision", strconv.Itoa(curl.Revision))
    83  	apiURI := url.URL{Path: "/migrate/charms", RawQuery: args.Encode()}
    84  
    85  	contentType := "application/zip"
    86  	var resp params.CharmsResponse
    87  	if err := c.httpPost(modelUUID, content, apiURI.String(), contentType, &resp); err != nil {
    88  		return nil, errors.Trace(err)
    89  	}
    90  
    91  	curl, err := charm.ParseURL(resp.CharmURL)
    92  	if err != nil {
    93  		return nil, errors.Annotatef(err, "bad charm URL in response")
    94  	}
    95  	return curl, nil
    96  }
    97  
    98  // UploadTools uploads tools at the specified location to the API server over HTTPS
    99  // for the specified model.
   100  func (c *Client) UploadTools(modelUUID string, r io.ReadSeeker, vers version.Binary, additionalSeries ...string) (tools.List, error) {
   101  	endpoint := fmt.Sprintf("/migrate/tools?binaryVersion=%s&series=%s", vers, strings.Join(additionalSeries, ","))
   102  	contentType := "application/x-tar-gz"
   103  	var resp params.ToolsResult
   104  	if err := c.httpPost(modelUUID, r, endpoint, contentType, &resp); err != nil {
   105  		return nil, errors.Trace(err)
   106  	}
   107  	return resp.ToolsList, nil
   108  }
   109  
   110  // UploadResource uploads a resource to the migration endpoint.
   111  func (c *Client) UploadResource(modelUUID string, res resource.Resource, r io.ReadSeeker) error {
   112  	args := makeResourceArgs(res)
   113  	args.Add("application", res.ApplicationID)
   114  	err := c.resourcePost(modelUUID, args, r)
   115  	return errors.Trace(err)
   116  }
   117  
   118  // SetPlaceholderResource sets the metadata for a placeholder resource.
   119  func (c *Client) SetPlaceholderResource(modelUUID string, res resource.Resource) error {
   120  	args := makeResourceArgs(res)
   121  	args.Add("application", res.ApplicationID)
   122  	err := c.resourcePost(modelUUID, args, nil)
   123  	return errors.Trace(err)
   124  }
   125  
   126  // SetUnitResource sets the metadata for a particular unit resource.
   127  func (c *Client) SetUnitResource(modelUUID, unit string, res resource.Resource) error {
   128  	args := makeResourceArgs(res)
   129  	args.Add("unit", unit)
   130  	err := c.resourcePost(modelUUID, args, nil)
   131  	return errors.Trace(err)
   132  }
   133  
   134  func (c *Client) resourcePost(modelUUID string, args url.Values, r io.ReadSeeker) error {
   135  	uri := "/migrate/resources?" + args.Encode()
   136  	if r == nil {
   137  		r = strings.NewReader("")
   138  	}
   139  	contentType := "application/octet-stream"
   140  	err := c.httpPost(modelUUID, r, uri, contentType, nil)
   141  	return errors.Trace(err)
   142  }
   143  
   144  func makeResourceArgs(res resource.Resource) url.Values {
   145  	args := url.Values{}
   146  	args.Add("name", res.Name)
   147  	args.Add("type", res.Type.String())
   148  	args.Add("path", res.Path)
   149  	args.Add("description", res.Description)
   150  	args.Add("origin", res.Origin.String())
   151  	args.Add("revision", fmt.Sprintf("%d", res.Revision))
   152  	args.Add("size", fmt.Sprintf("%d", res.Size))
   153  	args.Add("fingerprint", res.Fingerprint.Hex())
   154  	if res.Username != "" {
   155  		args.Add("user", res.Username)
   156  	}
   157  	if !res.IsPlaceholder() {
   158  		args.Add("timestamp", fmt.Sprint(res.Timestamp.UnixNano()))
   159  	}
   160  	return args
   161  }
   162  
   163  func (c *Client) httpPost(modelUUID string, content io.ReadSeeker, endpoint, contentType string, response interface{}) error {
   164  	req, err := http.NewRequest("POST", endpoint, nil)
   165  	if err != nil {
   166  		return errors.Annotate(err, "cannot create upload request")
   167  	}
   168  	req.Header.Set("Content-Type", contentType)
   169  	req.Header.Set(params.MigrationModelHTTPHeader, modelUUID)
   170  
   171  	// The returned httpClient sets the base url to /model/<uuid> if it can.
   172  	httpClient, err := c.httpClientFactory()
   173  	if err != nil {
   174  		return errors.Trace(err)
   175  	}
   176  
   177  	if err := httpClient.Do(req, content, response); err != nil {
   178  		return errors.Trace(err)
   179  	}
   180  	return nil
   181  }
   182  
   183  // OpenLogTransferStream connects to the migration logtransfer
   184  // endpoint on the target controller and returns a stream that JSON
   185  // logs records can be fed into. The objects written should be params.LogRecords.
   186  func (c *Client) OpenLogTransferStream(modelUUID string) (base.Stream, error) {
   187  	attrs := url.Values{}
   188  	attrs.Set("jujuclientversion", jujuversion.Current.String())
   189  	headers := http.Header{}
   190  	headers.Set(params.MigrationModelHTTPHeader, modelUUID)
   191  	caller := c.caller.RawAPICaller()
   192  	stream, err := caller.ConnectControllerStream("/migrate/logtransfer", attrs, headers)
   193  	if err != nil {
   194  		return nil, errors.Trace(err)
   195  	}
   196  	return stream, nil
   197  }
   198  
   199  // LatestLogTime asks the target controller for the time of the latest
   200  // log record it has seen. This can be used to make the log transfer
   201  // restartable.
   202  func (c *Client) LatestLogTime(modelUUID string) (time.Time, error) {
   203  	var result time.Time
   204  	args := params.ModelArgs{names.NewModelTag(modelUUID).String()}
   205  	err := c.caller.FacadeCall("LatestLogTime", args, &result)
   206  	if err != nil {
   207  		return time.Time{}, errors.Trace(err)
   208  	}
   209  	return result, nil
   210  }
   211  
   212  // AdoptResources asks the cloud provider to update the controller
   213  // tags for a model's resources. This prevents the resources from
   214  // being destroyed if the source controller is destroyed after the
   215  // model is migrated away.
   216  func (c *Client) AdoptResources(modelUUID string) error {
   217  	args := params.AdoptResourcesArgs{
   218  		ModelTag:                names.NewModelTag(modelUUID).String(),
   219  		SourceControllerVersion: jujuversion.Current,
   220  	}
   221  	return errors.Trace(c.caller.FacadeCall("AdoptResources", args, nil))
   222  }
   223  
   224  // CACert returns the CA certificate associated with
   225  // the connection.
   226  func (c *Client) CACert() (string, error) {
   227  	var result params.BytesResult
   228  	err := c.caller.FacadeCall("CACert", nil, &result)
   229  	if err != nil {
   230  		return "", err
   231  	}
   232  	return string(result.Result), nil
   233  }
   234  
   235  // CheckMachines compares the machines in state with the ones reported
   236  // by the provider and reports any discrepancies.
   237  func (c *Client) CheckMachines(modelUUID string) ([]error, error) {
   238  	var result params.ErrorResults
   239  	args := params.ModelArgs{names.NewModelTag(modelUUID).String()}
   240  	err := c.caller.FacadeCall("CheckMachines", args, &result)
   241  	if err != nil {
   242  		return nil, errors.Trace(err)
   243  	}
   244  	var results []error
   245  	for _, res := range result.Results {
   246  		results = append(results, errors.Errorf(res.Error.Message))
   247  	}
   248  	return results, nil
   249  }