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 }