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  }