github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/migrationmaster/client.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package migrationmaster 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "io" 10 "net/http" 11 "time" 12 13 "github.com/juju/errors" 14 "github.com/juju/httprequest" 15 "github.com/juju/version" 16 charmresource "gopkg.in/juju/charm.v6/resource" 17 "gopkg.in/juju/names.v2" 18 "gopkg.in/macaroon.v2-unstable" 19 20 "github.com/juju/juju/api/base" 21 "github.com/juju/juju/api/common" 22 "github.com/juju/juju/apiserver/params" 23 "github.com/juju/juju/core/migration" 24 "github.com/juju/juju/core/watcher" 25 "github.com/juju/juju/resource" 26 ) 27 28 // NewWatcherFunc exists to let us unit test Facade without patching. 29 type NewWatcherFunc func(base.APICaller, params.NotifyWatchResult) watcher.NotifyWatcher 30 31 // NewClient returns a new Client based on an existing API connection. 32 func NewClient(caller base.APICaller, newWatcher NewWatcherFunc) *Client { 33 return &Client{ 34 caller: base.NewFacadeCaller(caller, "MigrationMaster"), 35 newWatcher: newWatcher, 36 httpClientFactory: caller.HTTPClient, 37 } 38 } 39 40 // Client describes the client side API for the MigrationMaster facade 41 // (used by the migrationmaster worker). 42 type Client struct { 43 caller base.FacadeCaller 44 newWatcher NewWatcherFunc 45 httpClientFactory func() (*httprequest.Client, error) 46 } 47 48 // Watch returns a watcher which reports when a migration is active 49 // for the model associated with the API connection. 50 func (c *Client) Watch() (watcher.NotifyWatcher, error) { 51 var result params.NotifyWatchResult 52 err := c.caller.FacadeCall("Watch", nil, &result) 53 if err != nil { 54 return nil, errors.Trace(err) 55 } 56 if result.Error != nil { 57 return nil, result.Error 58 } 59 return c.newWatcher(c.caller.RawAPICaller(), result), nil 60 } 61 62 // MigrationStatus returns the details and progress of the latest 63 // model migration. 64 func (c *Client) MigrationStatus() (migration.MigrationStatus, error) { 65 var empty migration.MigrationStatus 66 var status params.MasterMigrationStatus 67 err := c.caller.FacadeCall("MigrationStatus", nil, &status) 68 if err != nil { 69 return empty, errors.Trace(err) 70 } 71 72 modelTag, err := names.ParseModelTag(status.Spec.ModelTag) 73 if err != nil { 74 return empty, errors.Annotatef(err, "parsing model tag") 75 } 76 77 phase, ok := migration.ParsePhase(status.Phase) 78 if !ok { 79 return empty, errors.New("unable to parse phase") 80 } 81 82 target := status.Spec.TargetInfo 83 controllerTag, err := names.ParseControllerTag(target.ControllerTag) 84 if err != nil { 85 return empty, errors.Annotatef(err, "parsing controller tag") 86 } 87 88 authTag, err := names.ParseUserTag(target.AuthTag) 89 if err != nil { 90 return empty, errors.Annotatef(err, "unable to parse auth tag") 91 } 92 93 var macs []macaroon.Slice 94 if target.Macaroons != "" { 95 if err := json.Unmarshal([]byte(target.Macaroons), &macs); err != nil { 96 return empty, errors.Annotatef(err, "unmarshalling macaroon") 97 } 98 } 99 100 return migration.MigrationStatus{ 101 MigrationId: status.MigrationId, 102 ModelUUID: modelTag.Id(), 103 Phase: phase, 104 PhaseChangedTime: status.PhaseChangedTime, 105 TargetInfo: migration.TargetInfo{ 106 ControllerTag: controllerTag, 107 Addrs: target.Addrs, 108 CACert: target.CACert, 109 AuthTag: authTag, 110 Password: target.Password, 111 Macaroons: macs, 112 }, 113 }, nil 114 } 115 116 // SetPhase updates the phase of the currently active model migration. 117 func (c *Client) SetPhase(phase migration.Phase) error { 118 args := params.SetMigrationPhaseArgs{ 119 Phase: phase.String(), 120 } 121 return c.caller.FacadeCall("SetPhase", args, nil) 122 } 123 124 // SetStatusMessage sets a human readable message regarding the 125 // progress of a migration. 126 func (c *Client) SetStatusMessage(message string) error { 127 args := params.SetMigrationStatusMessageArgs{ 128 Message: message, 129 } 130 return c.caller.FacadeCall("SetStatusMessage", args, nil) 131 } 132 133 // ModelInfo return basic information about the model to migrated. 134 func (c *Client) ModelInfo() (migration.ModelInfo, error) { 135 var info params.MigrationModelInfo 136 err := c.caller.FacadeCall("ModelInfo", nil, &info) 137 if err != nil { 138 return migration.ModelInfo{}, errors.Trace(err) 139 } 140 owner, err := names.ParseUserTag(info.OwnerTag) 141 if err != nil { 142 return migration.ModelInfo{}, errors.Trace(err) 143 } 144 return migration.ModelInfo{ 145 UUID: info.UUID, 146 Name: info.Name, 147 Owner: owner, 148 AgentVersion: info.AgentVersion, 149 ControllerAgentVersion: info.ControllerAgentVersion, 150 }, nil 151 } 152 153 // Prechecks verifies that the source controller and model are healthy 154 // and able to participate in a migration. 155 func (c *Client) Prechecks() error { 156 return c.caller.FacadeCall("Prechecks", nil, nil) 157 } 158 159 // Export returns a serialized representation of the model associated 160 // with the API connection. The charms used by the model are also 161 // returned. 162 func (c *Client) Export() (migration.SerializedModel, error) { 163 var empty migration.SerializedModel 164 var serialized params.SerializedModel 165 err := c.caller.FacadeCall("Export", nil, &serialized) 166 if err != nil { 167 return empty, errors.Trace(err) 168 } 169 170 // Convert tools info to output map. 171 tools := make(map[version.Binary]string) 172 for _, toolsInfo := range serialized.Tools { 173 v, err := version.ParseBinary(toolsInfo.Version) 174 if err != nil { 175 return migration.SerializedModel{}, errors.Annotate(err, "error parsing agent binary version") 176 } 177 tools[v] = toolsInfo.URI 178 } 179 180 resources, err := convertResources(serialized.Resources) 181 if err != nil { 182 return empty, errors.Trace(err) 183 } 184 185 return migration.SerializedModel{ 186 Bytes: serialized.Bytes, 187 Charms: serialized.Charms, 188 Tools: tools, 189 Resources: resources, 190 }, nil 191 } 192 193 // OpenResource downloads the named resource for an application. 194 func (c *Client) OpenResource(application, name string) (io.ReadCloser, error) { 195 httpClient, err := c.httpClientFactory() 196 if err != nil { 197 return nil, errors.Annotate(err, "unable to create HTTP client") 198 } 199 200 uri := fmt.Sprintf("/applications/%s/resources/%s", application, name) 201 var resp *http.Response 202 if err := httpClient.Get(uri, &resp); err != nil { 203 return nil, errors.Annotate(err, "unable to retrieve resource") 204 } 205 return resp.Body, nil 206 } 207 208 // Reap removes the documents for the model associated with the API 209 // connection. 210 func (c *Client) Reap() error { 211 return c.caller.FacadeCall("Reap", nil, nil) 212 } 213 214 // WatchMinionReports returns a watcher which reports when a migration 215 // minion has made a report for the current migration phase. 216 func (c *Client) WatchMinionReports() (watcher.NotifyWatcher, error) { 217 var result params.NotifyWatchResult 218 err := c.caller.FacadeCall("WatchMinionReports", nil, &result) 219 if err != nil { 220 return nil, errors.Trace(err) 221 } 222 if result.Error != nil { 223 return nil, result.Error 224 } 225 return c.newWatcher(c.caller.RawAPICaller(), result), nil 226 } 227 228 // MinionReports returns details of the reports made by migration 229 // minions to the controller for the current migration phase. 230 func (c *Client) MinionReports() (migration.MinionReports, error) { 231 var in params.MinionReports 232 var out migration.MinionReports 233 234 err := c.caller.FacadeCall("MinionReports", nil, &in) 235 if err != nil { 236 return out, errors.Trace(err) 237 } 238 239 out.MigrationId = in.MigrationId 240 241 phase, ok := migration.ParsePhase(in.Phase) 242 if !ok { 243 return out, errors.Errorf("invalid phase: %q", in.Phase) 244 } 245 out.Phase = phase 246 247 out.SuccessCount = in.SuccessCount 248 out.UnknownCount = in.UnknownCount 249 250 out.SomeUnknownMachines, out.SomeUnknownUnits, out.SomeUnknownApplications, err = groupTagIds(in.UnknownSample) 251 if err != nil { 252 return out, errors.Annotate(err, "processing unknown agents") 253 } 254 255 out.FailedMachines, out.FailedUnits, out.FailedApplications, err = groupTagIds(in.Failed) 256 if err != nil { 257 return out, errors.Annotate(err, "processing failed agents") 258 } 259 260 return out, nil 261 } 262 263 // StreamModelLog takes a starting time and returns a channel that 264 // will yield the logs on or after that time - these are the logs that 265 // need to be transferred to the target after the migration is 266 // successful. 267 func (c *Client) StreamModelLog(start time.Time) (<-chan common.LogMessage, error) { 268 return common.StreamDebugLog(c.caller.RawAPICaller(), common.DebugLogParams{ 269 Replay: true, 270 NoTail: true, 271 StartTime: start, 272 }) 273 } 274 275 func groupTagIds(tagStrs []string) ([]string, []string, []string, error) { 276 var machines []string 277 var units []string 278 var applications []string 279 280 for i := 0; i < len(tagStrs); i++ { 281 tag, err := names.ParseTag(tagStrs[i]) 282 if err != nil { 283 return nil, nil, nil, errors.Trace(err) 284 } 285 switch t := tag.(type) { 286 case names.MachineTag: 287 machines = append(machines, t.Id()) 288 case names.UnitTag: 289 units = append(units, t.Id()) 290 case names.ApplicationTag: 291 applications = append(applications, t.Id()) 292 default: 293 return nil, nil, nil, errors.Errorf("unsupported tag: %q", tag) 294 } 295 } 296 return machines, units, applications, nil 297 } 298 299 func convertResources(in []params.SerializedModelResource) ([]migration.SerializedModelResource, error) { 300 if len(in) == 0 { 301 return nil, nil 302 } 303 out := make([]migration.SerializedModelResource, 0, len(in)) 304 for _, resource := range in { 305 outResource, err := convertAppResource(resource) 306 if err != nil { 307 return nil, errors.Trace(err) 308 } 309 out = append(out, outResource) 310 } 311 return out, nil 312 } 313 314 func convertAppResource(in params.SerializedModelResource) (migration.SerializedModelResource, error) { 315 var empty migration.SerializedModelResource 316 appRev, err := convertResourceRevision(in.Application, in.Name, in.ApplicationRevision) 317 if err != nil { 318 return empty, errors.Annotate(err, "application revision") 319 } 320 csRev, err := convertResourceRevision(in.Application, in.Name, in.CharmStoreRevision) 321 if err != nil { 322 return empty, errors.Annotate(err, "charmstore revision") 323 } 324 unitRevs := make(map[string]resource.Resource) 325 for unitName, inUnitRev := range in.UnitRevisions { 326 unitRev, err := convertResourceRevision(in.Application, in.Name, inUnitRev) 327 if err != nil { 328 return empty, errors.Annotate(err, "unit revision") 329 } 330 unitRevs[unitName] = unitRev 331 } 332 return migration.SerializedModelResource{ 333 ApplicationRevision: appRev, 334 CharmStoreRevision: csRev, 335 UnitRevisions: unitRevs, 336 }, nil 337 } 338 339 func convertResourceRevision(app, name string, rev params.SerializedModelResourceRevision) (resource.Resource, error) { 340 var empty resource.Resource 341 type_, err := charmresource.ParseType(rev.Type) 342 if err != nil { 343 return empty, errors.Trace(err) 344 } 345 origin, err := charmresource.ParseOrigin(rev.Origin) 346 if err != nil { 347 return empty, errors.Trace(err) 348 } 349 var fp charmresource.Fingerprint 350 if rev.FingerprintHex != "" { 351 if fp, err = charmresource.ParseFingerprint(rev.FingerprintHex); err != nil { 352 return empty, errors.Annotate(err, "invalid fingerprint") 353 } 354 } 355 return resource.Resource{ 356 Resource: charmresource.Resource{ 357 Meta: charmresource.Meta{ 358 Name: name, 359 Type: type_, 360 Path: rev.Path, 361 Description: rev.Description, 362 }, 363 Origin: origin, 364 Revision: rev.Revision, 365 Size: rev.Size, 366 Fingerprint: fp, 367 }, 368 ApplicationID: app, 369 Username: rev.Username, 370 Timestamp: rev.Timestamp, 371 }, nil 372 }