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 }