github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/api/controller/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  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/names/v5"
    15  	"github.com/juju/version/v2"
    16  	"gopkg.in/httprequest.v1"
    17  
    18  	"github.com/juju/juju/api"
    19  	"github.com/juju/juju/api/base"
    20  	coremigration "github.com/juju/juju/core/migration"
    21  	"github.com/juju/juju/core/resources"
    22  	"github.com/juju/juju/rpc/params"
    23  	"github.com/juju/juju/tools"
    24  	jujuversion "github.com/juju/juju/version"
    25  )
    26  
    27  // NewClient returns a new Client based on an existing API connection.
    28  func NewClient(caller base.APICaller) *Client {
    29  	return &Client{
    30  		caller:                base.NewFacadeCaller(caller, "MigrationTarget"),
    31  		httpRootClientFactory: caller.RootHTTPClient,
    32  	}
    33  }
    34  
    35  // Client is the client-side API for the MigrationTarget facade. It is
    36  // used by the migrationmaster worker when talking to the target
    37  // controller during a migration.
    38  type Client struct {
    39  	caller                base.FacadeCaller
    40  	httpRootClientFactory func() (*httprequest.Client, error)
    41  }
    42  
    43  // BestFacadeVersion returns the best supported facade version
    44  // on the target controller.
    45  func (c *Client) BestFacadeVersion() int {
    46  	return c.caller.BestAPIVersion()
    47  }
    48  
    49  // Prechecks checks that the target controller is able to accept the
    50  // model being migrated.
    51  func (c *Client) Prechecks(model coremigration.ModelInfo) error {
    52  	// Pass all the known facade versions to the controller so that it
    53  	// can check that the target controller supports them. Passing all of them
    54  	// ensures that we don't have to update this code when new facades are
    55  	// added, or if the controller wants to change the logic service side.
    56  	supported := api.SupportedFacadeVersions()
    57  	versions := make(map[string][]int, len(supported))
    58  	for name, version := range supported {
    59  		versions[name] = version
    60  	}
    61  
    62  	args := params.MigrationModelInfo{
    63  		UUID:                   model.UUID,
    64  		Name:                   model.Name,
    65  		OwnerTag:               model.Owner.String(),
    66  		AgentVersion:           model.AgentVersion,
    67  		ControllerAgentVersion: model.ControllerAgentVersion,
    68  		FacadeVersions:         versions,
    69  	}
    70  	return errors.Trace(c.caller.FacadeCall("Prechecks", args, nil))
    71  }
    72  
    73  // Import takes a serialized model and imports it into the target
    74  // controller.
    75  func (c *Client) Import(bytes []byte) error {
    76  	serialized := params.SerializedModel{Bytes: bytes}
    77  	return errors.Trace(c.caller.FacadeCall("Import", serialized, nil))
    78  }
    79  
    80  // Abort removes all data relating to a previously imported model.
    81  func (c *Client) Abort(modelUUID string) error {
    82  	args := params.ModelArgs{ModelTag: names.NewModelTag(modelUUID).String()}
    83  	return errors.Trace(c.caller.FacadeCall("Abort", args, nil))
    84  }
    85  
    86  // Activate marks a migrated model as being ready to use.
    87  func (c *Client) Activate(modelUUID string, sourceInfo coremigration.SourceControllerInfo, relatedModels []string) error {
    88  	if c.caller.BestAPIVersion() < 2 {
    89  		args := params.ModelArgs{ModelTag: names.NewModelTag(modelUUID).String()}
    90  		return errors.Trace(c.caller.FacadeCall("Activate", args, nil))
    91  	}
    92  	args := params.ActivateModelArgs{
    93  		ModelTag: names.NewModelTag(modelUUID).String(),
    94  	}
    95  	if len(relatedModels) > 0 {
    96  		args.ControllerTag = sourceInfo.ControllerTag.String()
    97  		args.ControllerAlias = sourceInfo.ControllerAlias
    98  		args.SourceAPIAddrs = sourceInfo.Addrs
    99  		args.SourceCACert = sourceInfo.CACert
   100  		args.CrossModelUUIDs = relatedModels
   101  	}
   102  	return errors.Trace(c.caller.FacadeCall("Activate", args, nil))
   103  }
   104  
   105  // UploadCharm sends the content to the API server using an HTTP post in order
   106  // to add the charm binary to the model specified.
   107  func (c *Client) UploadCharm(modelUUID string, curl string, charmRef string, content io.ReadSeeker) (string, error) {
   108  	apiURI := url.URL{Path: fmt.Sprintf("/migrate/charms/%s", charmRef)}
   109  
   110  	contentType := "application/zip"
   111  	resp := &http.Response{}
   112  	// Add Juju-Curl header to Put operation. Juju 3.4 apiserver
   113  	// expects this header to be present, since we still need some
   114  	// of the values from the charm url.
   115  	headers := map[string]string{
   116  		"Juju-Curl": curl,
   117  	}
   118  	if err := c.httpPut(modelUUID, content, apiURI.String(), contentType, headers, &resp); err != nil {
   119  		return "", errors.Trace(err)
   120  	}
   121  
   122  	respCurl := resp.Header.Get("Juju-Curl")
   123  	if respCurl == "" {
   124  		return "", errors.Errorf("response returned no charm URL")
   125  	}
   126  	return respCurl, nil
   127  }
   128  
   129  // UploadTools uploads tools at the specified location to the API server over HTTPS
   130  // for the specified model.
   131  func (c *Client) UploadTools(modelUUID string, r io.ReadSeeker, vers version.Binary) (tools.List, error) {
   132  	endpoint := fmt.Sprintf("/migrate/tools?binaryVersion=%s", vers)
   133  	contentType := "application/x-tar-gz"
   134  	var resp params.ToolsResult
   135  	if err := c.httpPost(modelUUID, r, endpoint, contentType, nil, &resp); err != nil {
   136  		return nil, errors.Trace(err)
   137  	}
   138  	return resp.ToolsList, nil
   139  }
   140  
   141  // UploadResource uploads a resource to the migration endpoint.
   142  func (c *Client) UploadResource(modelUUID string, res resources.Resource, r io.ReadSeeker) error {
   143  	args := makeResourceArgs(res)
   144  	args.Add("application", res.ApplicationID)
   145  	err := c.resourcePost(modelUUID, args, r)
   146  	return errors.Trace(err)
   147  }
   148  
   149  // SetPlaceholderResource sets the metadata for a placeholder resource.
   150  func (c *Client) SetPlaceholderResource(modelUUID string, res resources.Resource) error {
   151  	args := makeResourceArgs(res)
   152  	args.Add("application", res.ApplicationID)
   153  	err := c.resourcePost(modelUUID, args, nil)
   154  	return errors.Trace(err)
   155  }
   156  
   157  // SetUnitResource sets the metadata for a particular unit resource.
   158  func (c *Client) SetUnitResource(modelUUID, unit string, res resources.Resource) error {
   159  	args := makeResourceArgs(res)
   160  	args.Add("unit", unit)
   161  	err := c.resourcePost(modelUUID, args, nil)
   162  	return errors.Trace(err)
   163  }
   164  
   165  func (c *Client) resourcePost(modelUUID string, args url.Values, r io.ReadSeeker) error {
   166  	uri := "/migrate/resources?" + args.Encode()
   167  	contentType := "application/octet-stream"
   168  	err := c.httpPost(modelUUID, r, uri, contentType, nil, nil)
   169  	return errors.Trace(err)
   170  }
   171  
   172  func makeResourceArgs(res resources.Resource) url.Values {
   173  	args := url.Values{}
   174  	args.Add("name", res.Name)
   175  	args.Add("type", res.Type.String())
   176  	args.Add("path", res.Path)
   177  	args.Add("description", res.Description)
   178  	args.Add("origin", res.Origin.String())
   179  	args.Add("revision", fmt.Sprintf("%d", res.Revision))
   180  	args.Add("size", fmt.Sprintf("%d", res.Size))
   181  	args.Add("fingerprint", res.Fingerprint.Hex())
   182  	if res.Username != "" {
   183  		args.Add("user", res.Username)
   184  	}
   185  	if !res.IsPlaceholder() {
   186  		args.Add("timestamp", fmt.Sprint(res.Timestamp.UnixNano()))
   187  	}
   188  	return args
   189  }
   190  
   191  func (c *Client) httpPost(modelUUID string, content io.ReadSeeker, endpoint, contentType string, headers map[string]string, response interface{}) error {
   192  	return c.http("POST", modelUUID, content, endpoint, contentType, headers, response)
   193  }
   194  
   195  func (c *Client) httpPut(modelUUID string, content io.ReadSeeker, endpoint, contentType string, headers map[string]string, response interface{}) error {
   196  	return c.http("PUT", modelUUID, content, endpoint, contentType, headers, response)
   197  }
   198  
   199  func (c *Client) http(method, modelUUID string, content io.ReadSeeker, endpoint, contentType string, headers map[string]string, response interface{}) error {
   200  	req, err := http.NewRequest(method, endpoint, content)
   201  	if err != nil {
   202  		return errors.Annotate(err, "cannot create upload request")
   203  	}
   204  	req.Header.Set("Content-Type", contentType)
   205  	req.Header.Set(params.MigrationModelHTTPHeader, modelUUID)
   206  	for k, v := range headers {
   207  		req.Header.Set(k, v)
   208  	}
   209  
   210  	// The returned httpClient sets the base url to the controller api root
   211  	httpClient, err := c.httpRootClientFactory()
   212  	if err != nil {
   213  		return errors.Trace(err)
   214  	}
   215  
   216  	return errors.Trace(httpClient.Do(c.caller.RawAPICaller().Context(), req, response))
   217  }
   218  
   219  // OpenLogTransferStream connects to the migration logtransfer
   220  // endpoint on the target controller and returns a stream that JSON
   221  // logs records can be fed into. The objects written should be params.LogRecords.
   222  func (c *Client) OpenLogTransferStream(modelUUID string) (base.Stream, error) {
   223  	headers := http.Header{}
   224  	headers.Set(params.MigrationModelHTTPHeader, modelUUID)
   225  	caller := c.caller.RawAPICaller()
   226  	stream, err := caller.ConnectControllerStream("/migrate/logtransfer", url.Values{}, headers)
   227  	if err != nil {
   228  		return nil, errors.Trace(err)
   229  	}
   230  	return stream, nil
   231  }
   232  
   233  // LatestLogTime asks the target controller for the time of the latest
   234  // log record it has seen. This can be used to make the log transfer
   235  // restartable.
   236  func (c *Client) LatestLogTime(modelUUID string) (time.Time, error) {
   237  	var result time.Time
   238  	args := params.ModelArgs{ModelTag: names.NewModelTag(modelUUID).String()}
   239  	err := c.caller.FacadeCall("LatestLogTime", args, &result)
   240  	if err != nil {
   241  		return time.Time{}, errors.Trace(err)
   242  	}
   243  	return result, nil
   244  }
   245  
   246  // AdoptResources asks the cloud provider to update the controller
   247  // tags for a model's resources. This prevents the resources from
   248  // being destroyed if the source controller is destroyed after the
   249  // model is migrated away.
   250  func (c *Client) AdoptResources(modelUUID string) error {
   251  	args := params.AdoptResourcesArgs{
   252  		ModelTag:                names.NewModelTag(modelUUID).String(),
   253  		SourceControllerVersion: jujuversion.Current,
   254  	}
   255  	return errors.Trace(c.caller.FacadeCall("AdoptResources", args, nil))
   256  }
   257  
   258  // CACert returns the CA certificate associated with
   259  // the connection.
   260  func (c *Client) CACert() (string, error) {
   261  	var result params.BytesResult
   262  	err := c.caller.FacadeCall("CACert", nil, &result)
   263  	if err != nil {
   264  		return "", errors.Trace(err)
   265  	}
   266  	return string(result.Result), nil
   267  }
   268  
   269  // CheckMachines compares the machines in state with the ones reported
   270  // by the provider and reports any discrepancies.
   271  func (c *Client) CheckMachines(modelUUID string) ([]error, error) {
   272  	var result params.ErrorResults
   273  	args := params.ModelArgs{ModelTag: names.NewModelTag(modelUUID).String()}
   274  	err := c.caller.FacadeCall("CheckMachines", args, &result)
   275  	if err != nil {
   276  		return nil, errors.Trace(err)
   277  	}
   278  	var results []error
   279  	for _, res := range result.Results {
   280  		results = append(results, errors.Errorf(res.Error.Message))
   281  	}
   282  	return results, nil
   283  }