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 }