github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/apiserver/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/names"
    10  	"github.com/juju/version"
    11  
    12  	"github.com/juju/juju/apiserver/common"
    13  	"github.com/juju/juju/apiserver/params"
    14  	"github.com/juju/juju/environs/config"
    15  	"github.com/juju/juju/state"
    16  	"github.com/juju/juju/state/watcher"
    17  	jujuversion "github.com/juju/juju/version"
    18  )
    19  
    20  var logger = loggo.GetLogger("juju.apiserver.upgrader")
    21  
    22  func init() {
    23  	common.RegisterStandardFacade("Upgrader", 1, upgraderFacade)
    24  }
    25  
    26  // upgraderFacade is a bit unique vs the other API Facades, as it has two
    27  // implementations that actually expose the same API and which one gets
    28  // returned depends on who is calling.
    29  // Both of them conform to the exact Upgrader API, so the actual calls that are
    30  // available do not depend on who is currently connected.
    31  func upgraderFacade(st *state.State, resources *common.Resources, auth common.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  	resources  *common.Resources
    65  	authorizer common.Authorizer
    66  }
    67  
    68  // NewUpgraderAPI creates a new server-side UpgraderAPI facade.
    69  func NewUpgraderAPI(
    70  	st *state.State,
    71  	resources *common.Resources,
    72  	authorizer common.Authorizer,
    73  ) (*UpgraderAPI, error) {
    74  	if !authorizer.AuthMachineAgent() {
    75  		return nil, common.ErrPerm
    76  	}
    77  	getCanReadWrite := func() (common.AuthFunc, error) {
    78  		return authorizer.AuthOwner, nil
    79  	}
    80  	env, err := st.Model()
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	urlGetter := common.NewToolsURLGetter(env.UUID(), st)
    85  	return &UpgraderAPI{
    86  		ToolsGetter: common.NewToolsGetter(st, st, st, urlGetter, getCanReadWrite),
    87  		ToolsSetter: common.NewToolsSetter(st, getCanReadWrite),
    88  		st:          st,
    89  		resources:   resources,
    90  		authorizer:  authorizer,
    91  	}, nil
    92  }
    93  
    94  // WatchAPIVersion starts a watcher to track if there is a new version
    95  // of the API that we want to upgrade to
    96  func (u *UpgraderAPI) WatchAPIVersion(args params.Entities) (params.NotifyWatchResults, error) {
    97  	result := params.NotifyWatchResults{
    98  		Results: make([]params.NotifyWatchResult, len(args.Entities)),
    99  	}
   100  	for i, agent := range args.Entities {
   101  		tag, err := names.ParseTag(agent.Tag)
   102  		if err != nil {
   103  			return params.NotifyWatchResults{}, errors.Trace(err)
   104  		}
   105  		err = common.ErrPerm
   106  		if u.authorizer.AuthOwner(tag) {
   107  			watch := u.st.WatchForModelConfigChanges()
   108  			// Consume the initial event. Technically, API
   109  			// calls to Watch 'transmit' the initial event
   110  			// in the Watch response. But NotifyWatchers
   111  			// have no state to transmit.
   112  			if _, ok := <-watch.Changes(); ok {
   113  				result.Results[i].NotifyWatcherId = u.resources.Register(watch)
   114  				err = nil
   115  			} else {
   116  				err = watcher.EnsureErr(watch)
   117  			}
   118  		}
   119  		result.Results[i].Error = common.ServerError(err)
   120  	}
   121  	return result, nil
   122  }
   123  
   124  func (u *UpgraderAPI) getGlobalAgentVersion() (version.Number, *config.Config, error) {
   125  	// Get the Agent Version requested in the Environment Config
   126  	cfg, err := u.st.ModelConfig()
   127  	if err != nil {
   128  		return version.Number{}, nil, err
   129  	}
   130  	agentVersion, ok := cfg.AgentVersion()
   131  	if !ok {
   132  		return version.Number{}, nil, errors.New("agent version not set in model config")
   133  	}
   134  	return agentVersion, cfg, nil
   135  }
   136  
   137  type hasIsManager interface {
   138  	IsManager() bool
   139  }
   140  
   141  func (u *UpgraderAPI) entityIsManager(tag names.Tag) bool {
   142  	entity, err := u.st.FindEntity(tag)
   143  	if err != nil {
   144  		return false
   145  	}
   146  	if m, ok := entity.(hasIsManager); !ok {
   147  		return false
   148  	} else {
   149  		return m.IsManager()
   150  	}
   151  }
   152  
   153  // DesiredVersion reports the Agent Version that we want that agent to be running
   154  func (u *UpgraderAPI) DesiredVersion(args params.Entities) (params.VersionResults, error) {
   155  	results := make([]params.VersionResult, len(args.Entities))
   156  	if len(args.Entities) == 0 {
   157  		return params.VersionResults{}, nil
   158  	}
   159  	agentVersion, _, err := u.getGlobalAgentVersion()
   160  	if err != nil {
   161  		return params.VersionResults{}, common.ServerError(err)
   162  	}
   163  	// Is the desired version greater than the current API server version?
   164  	isNewerVersion := agentVersion.Compare(jujuversion.Current) > 0
   165  	for i, entity := range args.Entities {
   166  		tag, err := names.ParseTag(entity.Tag)
   167  		if err != nil {
   168  			results[i].Error = common.ServerError(err)
   169  			continue
   170  		}
   171  		err = common.ErrPerm
   172  		if u.authorizer.AuthOwner(tag) {
   173  			// Only return the globally desired agent version if the
   174  			// asking entity is a machine agent with JobManageModel or
   175  			// if this API server is running the globally desired agent
   176  			// version. Otherwise report this API server's current
   177  			// agent version.
   178  			//
   179  			// This ensures that state machine agents will upgrade
   180  			// first - once they have restarted and are running the
   181  			// new version other agents will start to see the new
   182  			// agent version.
   183  			if !isNewerVersion || u.entityIsManager(tag) {
   184  				results[i].Version = &agentVersion
   185  			} else {
   186  				logger.Debugf("desired version is %s, but current version is %s and agent is not a manager node", agentVersion, jujuversion.Current)
   187  				results[i].Version = &jujuversion.Current
   188  			}
   189  			err = nil
   190  		}
   191  		results[i].Error = common.ServerError(err)
   192  	}
   193  	return params.VersionResults{Results: results}, nil
   194  }