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