github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/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  	"errors"
     8  
     9  	"github.com/juju/loggo"
    10  
    11  	"github.com/juju/juju/environs/config"
    12  	"github.com/juju/juju/state"
    13  	"github.com/juju/juju/state/api/params"
    14  	"github.com/juju/juju/state/apiserver/common"
    15  	"github.com/juju/juju/state/watcher"
    16  	"github.com/juju/juju/version"
    17  )
    18  
    19  var logger = loggo.GetLogger("juju.state.apiserver.upgrader")
    20  
    21  type Upgrader interface {
    22  	WatchAPIVersion(args params.Entities) (params.NotifyWatchResults, error)
    23  	DesiredVersion(args params.Entities) (params.VersionResults, error)
    24  	Tools(args params.Entities) (params.ToolsResults, error)
    25  	SetTools(args params.EntitiesVersion) (params.ErrorResults, error)
    26  }
    27  
    28  // UpgraderAPI provides access to the Upgrader API facade.
    29  type UpgraderAPI struct {
    30  	*common.ToolsGetter
    31  	*common.ToolsSetter
    32  
    33  	st         *state.State
    34  	resources  *common.Resources
    35  	authorizer common.Authorizer
    36  }
    37  
    38  // NewUpgraderAPI creates a new client-side UpgraderAPI facade.
    39  func NewUpgraderAPI(
    40  	st *state.State,
    41  	resources *common.Resources,
    42  	authorizer common.Authorizer,
    43  ) (*UpgraderAPI, error) {
    44  	if !authorizer.AuthMachineAgent() {
    45  		return nil, common.ErrPerm
    46  	}
    47  	getCanReadWrite := func() (common.AuthFunc, error) {
    48  		return authorizer.AuthOwner, nil
    49  	}
    50  	return &UpgraderAPI{
    51  		ToolsGetter: common.NewToolsGetter(st, getCanReadWrite),
    52  		ToolsSetter: common.NewToolsSetter(st, getCanReadWrite),
    53  		st:          st,
    54  		resources:   resources,
    55  		authorizer:  authorizer,
    56  	}, nil
    57  }
    58  
    59  // WatchAPIVersion starts a watcher to track if there is a new version
    60  // of the API that we want to upgrade to
    61  func (u *UpgraderAPI) WatchAPIVersion(args params.Entities) (params.NotifyWatchResults, error) {
    62  	result := params.NotifyWatchResults{
    63  		Results: make([]params.NotifyWatchResult, len(args.Entities)),
    64  	}
    65  	for i, agent := range args.Entities {
    66  		err := common.ErrPerm
    67  		if u.authorizer.AuthOwner(agent.Tag) {
    68  			watch := u.st.WatchForEnvironConfigChanges()
    69  			// Consume the initial event. Technically, API
    70  			// calls to Watch 'transmit' the initial event
    71  			// in the Watch response. But NotifyWatchers
    72  			// have no state to transmit.
    73  			if _, ok := <-watch.Changes(); ok {
    74  				result.Results[i].NotifyWatcherId = u.resources.Register(watch)
    75  				err = nil
    76  			} else {
    77  				err = watcher.MustErr(watch)
    78  			}
    79  		}
    80  		result.Results[i].Error = common.ServerError(err)
    81  	}
    82  	return result, nil
    83  }
    84  
    85  func (u *UpgraderAPI) getGlobalAgentVersion() (version.Number, *config.Config, error) {
    86  	// Get the Agent Version requested in the Environment Config
    87  	cfg, err := u.st.EnvironConfig()
    88  	if err != nil {
    89  		return version.Number{}, nil, err
    90  	}
    91  	agentVersion, ok := cfg.AgentVersion()
    92  	if !ok {
    93  		return version.Number{}, nil, errors.New("agent version not set in environment config")
    94  	}
    95  	return agentVersion, cfg, nil
    96  }
    97  
    98  type hasIsManager interface {
    99  	IsManager() bool
   100  }
   101  
   102  func (u *UpgraderAPI) entityIsManager(tag string) bool {
   103  	entity, err := u.st.FindEntity(tag)
   104  	if err != nil {
   105  		return false
   106  	}
   107  	if m, ok := entity.(hasIsManager); !ok {
   108  		return false
   109  	} else {
   110  		return m.IsManager()
   111  	}
   112  }
   113  
   114  // DesiredVersion reports the Agent Version that we want that agent to be running
   115  func (u *UpgraderAPI) DesiredVersion(args params.Entities) (params.VersionResults, error) {
   116  	results := make([]params.VersionResult, len(args.Entities))
   117  	if len(args.Entities) == 0 {
   118  		return params.VersionResults{}, nil
   119  	}
   120  	agentVersion, _, err := u.getGlobalAgentVersion()
   121  	if err != nil {
   122  		return params.VersionResults{}, common.ServerError(err)
   123  	}
   124  	// Is the desired version greater than the current API server version?
   125  	isNewerVersion := agentVersion.Compare(version.Current.Number) > 0
   126  	for i, entity := range args.Entities {
   127  		err := common.ErrPerm
   128  		if u.authorizer.AuthOwner(entity.Tag) {
   129  			// Only return the globally desired agent version if the
   130  			// asking entity is a machine agent with JobManageEnviron or
   131  			// if this API server is running the globally desired agent
   132  			// version. Otherwise report this API server's current
   133  			// agent version.
   134  			//
   135  			// This ensures that state machine agents will upgrade
   136  			// first - once they have restarted and are running the
   137  			// new version other agents will start to see the new
   138  			// agent version.
   139  			if !isNewerVersion || u.entityIsManager(entity.Tag) {
   140  				results[i].Version = &agentVersion
   141  			} else {
   142  				logger.Debugf("desired version is %s, but current version is %s and agent is not a manager node", agentVersion, version.Current.Number)
   143  				results[i].Version = &version.Current.Number
   144  			}
   145  			err = nil
   146  		}
   147  		results[i].Error = common.ServerError(err)
   148  	}
   149  	return params.VersionResults{Results: results}, nil
   150  }