github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/agent/upgrader/upgrader.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package upgrader
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  	"github.com/juju/version"
    10  	"gopkg.in/juju/names.v2"
    11  
    12  	"github.com/juju/juju/apiserver/common"
    13  	"github.com/juju/juju/apiserver/facade"
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/environs/config"
    16  	"github.com/juju/juju/state"
    17  	"github.com/juju/juju/state/stateenvirons"
    18  	"github.com/juju/juju/state/watcher"
    19  	jujuversion "github.com/juju/juju/version"
    20  )
    21  
    22  var logger = loggo.GetLogger("juju.apiserver.upgrader")
    23  
    24  // The upgrader facade is a bit unique vs the other API Facades, as it
    25  // has two implementations that actually expose the same API and which
    26  // one gets returned depends on who is calling.  Both of them conform
    27  // to the exact Upgrader API, so the actual calls that are available
    28  // do not depend on who is currently connected.
    29  
    30  // NewUpgraderFacade provides the signature required for facade registration.
    31  func NewUpgraderFacade(st *state.State, resources facade.Resources, auth facade.Authorizer) (Upgrader, error) {
    32  	// The type of upgrader we return depends on who is asking.
    33  	// Machines get an UpgraderAPI, units get a UnitUpgraderAPI.
    34  	// This is tested in the api/upgrader package since there
    35  	// are currently no direct srvRoot tests.
    36  	// TODO(dfc) this is redundant
    37  	tag, err := names.ParseTag(auth.GetAuthTag().String())
    38  	if err != nil {
    39  		return nil, common.ErrPerm
    40  	}
    41  	switch tag.(type) {
    42  	case names.MachineTag:
    43  		return NewUpgraderAPI(st, resources, auth)
    44  	case names.UnitTag:
    45  		return NewUnitUpgraderAPI(st, resources, auth)
    46  	}
    47  	// Not a machine or unit.
    48  	return nil, common.ErrPerm
    49  }
    50  
    51  type Upgrader interface {
    52  	WatchAPIVersion(args params.Entities) (params.NotifyWatchResults, error)
    53  	DesiredVersion(args params.Entities) (params.VersionResults, error)
    54  	Tools(args params.Entities) (params.ToolsResults, error)
    55  	SetTools(args params.EntitiesVersion) (params.ErrorResults, error)
    56  }
    57  
    58  // UpgraderAPI provides access to the Upgrader API facade.
    59  type UpgraderAPI struct {
    60  	*common.ToolsGetter
    61  	*common.ToolsSetter
    62  
    63  	st         *state.State
    64  	m          *state.Model
    65  	resources  facade.Resources
    66  	authorizer facade.Authorizer
    67  }
    68  
    69  // NewUpgraderAPI creates a new server-side UpgraderAPI facade.
    70  func NewUpgraderAPI(
    71  	st *state.State,
    72  	resources facade.Resources,
    73  	authorizer facade.Authorizer,
    74  ) (*UpgraderAPI, error) {
    75  	if !authorizer.AuthMachineAgent() {
    76  		return nil, common.ErrPerm
    77  	}
    78  	getCanReadWrite := func() (common.AuthFunc, error) {
    79  		return authorizer.AuthOwner, nil
    80  	}
    81  	model, err := st.Model()
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	urlGetter := common.NewToolsURLGetter(model.UUID(), st)
    86  	configGetter := stateenvirons.EnvironConfigGetter{st, model}
    87  	return &UpgraderAPI{
    88  		ToolsGetter: common.NewToolsGetter(st, configGetter, st, urlGetter, getCanReadWrite),
    89  		ToolsSetter: common.NewToolsSetter(st, getCanReadWrite),
    90  		st:          st,
    91  		m:           model,
    92  		resources:   resources,
    93  		authorizer:  authorizer,
    94  	}, nil
    95  }
    96  
    97  // WatchAPIVersion starts a watcher to track if there is a new version
    98  // of the API that we want to upgrade to
    99  func (u *UpgraderAPI) WatchAPIVersion(args params.Entities) (params.NotifyWatchResults, error) {
   100  	result := params.NotifyWatchResults{
   101  		Results: make([]params.NotifyWatchResult, len(args.Entities)),
   102  	}
   103  	for i, agent := range args.Entities {
   104  		tag, err := names.ParseTag(agent.Tag)
   105  		if err != nil {
   106  			return params.NotifyWatchResults{}, errors.Trace(err)
   107  		}
   108  		err = common.ErrPerm
   109  		if u.authorizer.AuthOwner(tag) {
   110  			watch := u.m.WatchForModelConfigChanges()
   111  			// Consume the initial event. Technically, API
   112  			// calls to Watch 'transmit' the initial event
   113  			// in the Watch response. But NotifyWatchers
   114  			// have no state to transmit.
   115  			if _, ok := <-watch.Changes(); ok {
   116  				result.Results[i].NotifyWatcherId = u.resources.Register(watch)
   117  				err = nil
   118  			} else {
   119  				err = watcher.EnsureErr(watch)
   120  			}
   121  		}
   122  		result.Results[i].Error = common.ServerError(err)
   123  	}
   124  	return result, nil
   125  }
   126  
   127  func (u *UpgraderAPI) getGlobalAgentVersion() (version.Number, *config.Config, error) {
   128  	// Get the Agent Version requested in the Model Config
   129  	cfg, err := u.m.ModelConfig()
   130  	if err != nil {
   131  		return version.Number{}, nil, err
   132  	}
   133  	agentVersion, ok := cfg.AgentVersion()
   134  	if !ok {
   135  		return version.Number{}, nil, errors.New("agent version not set in model config")
   136  	}
   137  	return agentVersion, cfg, nil
   138  }
   139  
   140  type hasIsManager interface {
   141  	IsManager() bool
   142  }
   143  
   144  func (u *UpgraderAPI) entityIsManager(tag names.Tag) bool {
   145  	entity, err := u.st.FindEntity(tag)
   146  	if err != nil {
   147  		return false
   148  	}
   149  	if m, ok := entity.(hasIsManager); !ok {
   150  		return false
   151  	} else {
   152  		return m.IsManager()
   153  	}
   154  }
   155  
   156  // DesiredVersion reports the Agent Version that we want that agent to be running
   157  func (u *UpgraderAPI) DesiredVersion(args params.Entities) (params.VersionResults, error) {
   158  	results := make([]params.VersionResult, len(args.Entities))
   159  	if len(args.Entities) == 0 {
   160  		return params.VersionResults{}, nil
   161  	}
   162  	agentVersion, _, err := u.getGlobalAgentVersion()
   163  	if err != nil {
   164  		return params.VersionResults{}, common.ServerError(err)
   165  	}
   166  	// Is the desired version greater than the current API server version?
   167  	isNewerVersion := agentVersion.Compare(jujuversion.Current) > 0
   168  	for i, entity := range args.Entities {
   169  		tag, err := names.ParseTag(entity.Tag)
   170  		if err != nil {
   171  			results[i].Error = common.ServerError(err)
   172  			continue
   173  		}
   174  		err = common.ErrPerm
   175  		if u.authorizer.AuthOwner(tag) {
   176  			// Only return the globally desired agent version if the
   177  			// asking entity is a machine agent with JobManageModel or
   178  			// if this API server is running the globally desired agent
   179  			// version. Otherwise report this API server's current
   180  			// agent version.
   181  			//
   182  			// This ensures that state machine agents will upgrade
   183  			// first - once they have restarted and are running the
   184  			// new version other agents will start to see the new
   185  			// agent version.
   186  			if !isNewerVersion || u.entityIsManager(tag) {
   187  				results[i].Version = &agentVersion
   188  			} else {
   189  				logger.Debugf("desired version is %s, but current version is %s and agent is not a manager node", agentVersion, jujuversion.Current)
   190  				results[i].Version = &jujuversion.Current
   191  			}
   192  			err = nil
   193  		}
   194  		results[i].Error = common.ServerError(err)
   195  	}
   196  	return params.VersionResults{Results: results}, nil
   197  }